blob: 1ed5cd824050964b70d0e22d4b4f851b01d032a8 [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;
Madhava Srinivasan154e0192019-08-26 22:15:31 +020045import android.hardware.hdmi.IHdmiControlStatusChangeListener;
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +090046import android.hardware.hdmi.IHdmiDeviceEventListener;
Jungshik Jangd643f762014-05-22 19:28:09 +090047import android.hardware.hdmi.IHdmiHotplugEventListener;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090048import android.hardware.hdmi.IHdmiInputChangeListener;
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +090049import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090050import android.hardware.hdmi.IHdmiRecordListener;
Jungshik Jangea67c182014-06-19 22:19:20 +090051import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Jinsuk Kim119160a2014-07-07 18:48:10 +090052import android.hardware.hdmi.IHdmiVendorCommandListener;
Donghyun Chobc6e3722016-11-04 05:25:52 +090053import android.hardware.tv.cec.V1_0.OptionKey;
54import android.hardware.tv.cec.V1_0.SendMessageResult;
Jungshik Janga858d222014-06-23 17:17:47 +090055import android.media.AudioManager;
Jinsuk Kim7fa3a662014-11-07 15:20:24 +090056import android.media.tv.TvInputManager;
57import android.media.tv.TvInputManager.TvInputCallback;
Jinsuk Kim50084862014-08-07 13:11:40 +090058import android.net.Uri;
Amy014b22f2019-02-04 12:55:40 -080059import android.os.Binder;
Nick Chalko4797b7c2019-09-25 12:07:23 -070060import android.os.Build;
Jungshik Jang67ea5212014-05-15 14:05:24 +090061import android.os.Handler;
Jungshik Jang0792d372014-04-23 17:57:26 +090062import android.os.HandlerThread;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090063import android.os.IBinder;
Jungshik Jange9c77c82014-04-24 20:30:09 +090064import android.os.Looper;
Yuncheol Heo38db6292014-07-01 14:15:14 +090065import android.os.PowerManager;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090066import android.os.RemoteException;
Yuncheol Heo38db6292014-07-01 14:15:14 +090067import android.os.SystemClock;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090068import android.os.SystemProperties;
Jinsuk Kim50084862014-08-07 13:11:40 +090069import android.os.UserHandle;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090070import android.provider.Settings.Global;
Nick Chalko4797b7c2019-09-25 12:07:23 -070071import android.sysprop.HdmiProperties;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090072import android.text.TextUtils;
Jinsuk Kim2b152012014-07-25 08:22:26 +090073import android.util.ArraySet;
Jungshik Jang0792d372014-04-23 17:57:26 +090074import android.util.Slog;
Jungshik Jang3ee65722014-06-03 16:22:30 +090075import android.util.SparseArray;
Jungshik Jang8b308d92014-05-29 21:52:28 +090076import android.util.SparseIntArray;
Shubang Lu00b976a2018-08-01 18:11:46 -070077
Jinsuk Kim4893c7e2014-06-19 14:13:22 +090078import com.android.internal.annotations.GuardedBy;
Amy1d0b1372018-05-24 14:36:25 -070079import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060080import com.android.internal.util.DumpUtils;
Terry Heo959d2db2014-08-28 16:45:41 +090081import com.android.internal.util.IndentingPrintWriter;
Jungshik Jang0792d372014-04-23 17:57:26 +090082import com.android.server.SystemService;
Jungshik Janga5b74142014-06-23 18:03:10 +090083import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang3ee65722014-06-03 16:22:30 +090084import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
Jinsuk Kim7e742062014-07-30 13:19:13 +090085import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090086import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
Shubang Lu00b976a2018-08-01 18:11:46 -070087
88import libcore.util.EmptyArray;
89
Terry Heo959d2db2014-08-28 16:45:41 +090090import java.io.FileDescriptor;
91import java.io.PrintWriter;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090092import java.util.ArrayList;
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +090093import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090094import java.util.Collections;
Kyeongkab.Namec0aac92018-10-10 13:47:29 +090095import java.util.HashMap;
Jungshik Jang02bb4262014-05-23 16:48:31 +090096import java.util.List;
Terry Heo1ca0a432014-08-18 10:30:32 +090097import java.util.Locale;
Kyeongkab.Namec0aac92018-10-10 13:47:29 +090098import java.util.Map;
Nick Chalko4797b7c2019-09-25 12:07:23 -070099import java.util.Objects;
100import java.util.stream.Collectors;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900101
Jungshik Jang0792d372014-04-23 17:57:26 +0900102/**
103 * Provides a service for sending and processing HDMI control messages,
104 * HDMI-CEC and MHL control command, and providing the information on both standard.
105 */
Amyd4f98992018-05-11 17:59:06 -0700106public class HdmiControlService extends SystemService {
Jungshik Jang0792d372014-04-23 17:57:26 +0900107 private static final String TAG = "HdmiControlService";
Nick Chalko9bed7172020-02-11 13:09:03 -0800108 private static final Locale HONG_KONG = new Locale("zh", "HK");
109 private static final Locale MACAU = new Locale("zh", "MO");
Jungshik Jang0792d372014-04-23 17:57:26 +0900110
Nick Chalko9bed7172020-02-11 13:09:03 -0800111 private static final Map<String, String> sTerminologyToBibliographicMap =
112 createsTerminologyToBibliographicMap();
113
114 private static Map<String, String> createsTerminologyToBibliographicMap() {
115 Map<String, String> temp = new HashMap<>();
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900116 // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
Nick Chalko9bed7172020-02-11 13:09:03 -0800117 temp.put("sqi", "alb"); // Albanian
118 temp.put("hye", "arm"); // Armenian
119 temp.put("eus", "baq"); // Basque
120 temp.put("mya", "bur"); // Burmese
121 temp.put("ces", "cze"); // Czech
122 temp.put("nld", "dut"); // Dutch
123 temp.put("kat", "geo"); // Georgian
124 temp.put("deu", "ger"); // German
125 temp.put("ell", "gre"); // Greek
126 temp.put("fra", "fre"); // French
127 temp.put("isl", "ice"); // Icelandic
128 temp.put("mkd", "mac"); // Macedonian
129 temp.put("mri", "mao"); // Maori
130 temp.put("msa", "may"); // Malay
131 temp.put("fas", "per"); // Persian
132 temp.put("ron", "rum"); // Romanian
133 temp.put("slk", "slo"); // Slovak
134 temp.put("bod", "tib"); // Tibetan
135 temp.put("cym", "wel"); // Welsh
136 return Collections.unmodifiableMap(temp);
137 }
138
139 @VisibleForTesting static String localeToMenuLanguage(Locale locale) {
140 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
141 // Android always returns "zho" for all Chinese variants.
142 // Use "bibliographic" code defined in CEC639-2 for traditional
143 // Chinese used in Taiwan/Hong Kong/Macau.
144 return "chi";
145 } else {
146 String language = locale.getISO3Language();
147
148 // locale.getISO3Language() returns terminology code and need to
149 // send it as bibliographic code instead since the Bibliographic
150 // codes of ISO/FDIS 639-2 shall be used.
151 // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
152 // But, as it depends on the locale, is not handled here.
153 if (sTerminologyToBibliographicMap.containsKey(language)) {
154 language = sTerminologyToBibliographicMap.get(language);
155 }
156
157 return language;
158 }
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900159 }
160
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +0900161 static final String PERMISSION = "android.permission.HDMI_CEC";
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900162
Shubanga3f59502018-06-05 16:32:53 -0700163 // The reason code to initiate initializeCec().
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900164 static final int INITIATED_BY_ENABLE_CEC = 0;
165 static final int INITIATED_BY_BOOT_UP = 1;
166 static final int INITIATED_BY_SCREEN_ON = 2;
167 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
Yuncheol Heob5021862014-09-02 10:36:04 +0900168 static final int INITIATED_BY_HOTPLUG = 4;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900169
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900170 // The reason code representing the intent action that drives the standby
171 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
172 // Intent.ACTION_SHUTDOWN.
173 static final int STANDBY_SCREEN_OFF = 0;
174 static final int STANDBY_SHUTDOWN = 1;
175
Amy123ec402018-09-25 10:56:31 -0700176 // Logical address of the active source.
177 @GuardedBy("mLock")
178 protected final ActiveSource mActiveSource = new ActiveSource();
179
Marvin Raminda665a62020-03-16 14:17:58 +0100180 // Whether HDMI CEC volume control is enabled or not.
181 @GuardedBy("mLock")
182 private boolean mHdmiCecVolumeControlEnabled;
183
Amy489454f2019-01-24 19:06:57 -0800184 // Whether System Audio Mode is activated or not.
185 @GuardedBy("mLock")
186 private boolean mSystemAudioActivated = false;
187
Amy02f31152018-08-28 15:05:42 -0700188 private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
189 SystemProperties.getBoolean(
190 Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
191
Jungshik Jangd643f762014-05-22 19:28:09 +0900192 /**
193 * Interface to report send result.
194 */
195 interface SendMessageCallback {
196 /**
197 * Called when {@link HdmiControlService#sendCecCommand} is completed.
198 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900199 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900200 * <ul>
Donghyun Chobc6e3722016-11-04 05:25:52 +0900201 * <li>{@link SendMessageResult#SUCCESS}
202 * <li>{@link SendMessageResult#NACK}
203 * <li>{@link SendMessageResult#BUSY}
204 * <li>{@link SendMessageResult#FAIL}
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900205 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900206 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900207 void onSendCompleted(int error);
208 }
209
Jungshik Jang02bb4262014-05-23 16:48:31 +0900210 /**
211 * Interface to get a list of available logical devices.
212 */
213 interface DevicePollingCallback {
214 /**
215 * Called when device polling is finished.
216 *
217 * @param ackedAddress a list of logical addresses of available devices
218 */
219 void onPollingFinished(List<Integer> ackedAddress);
220 }
221
Terry Heo1ca0a432014-08-18 10:30:32 +0900222 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900223 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900224 @Override
225 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900226 assertRunOnServiceThread();
Amy3288f8a2018-10-23 18:55:34 -0700227 boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
Yuncheol Heo38db6292014-07-01 14:15:14 +0900228 switch (intent.getAction()) {
229 case Intent.ACTION_SCREEN_OFF:
Amy3288f8a2018-10-23 18:55:34 -0700230 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900231 onStandby(STANDBY_SCREEN_OFF);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900232 }
233 break;
234 case Intent.ACTION_SCREEN_ON:
235 if (isPowerStandbyOrTransient()) {
236 onWakeUp();
237 }
238 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900239 case Intent.ACTION_CONFIGURATION_CHANGED:
Nick Chalko9bed7172020-02-11 13:09:03 -0800240 String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault());
241 if (!mMenuLanguage.equals(language)) {
Terry Heo1ca0a432014-08-18 10:30:32 +0900242 onLanguageChanged(language);
243 }
244 break;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900245 case Intent.ACTION_SHUTDOWN:
Amy3288f8a2018-10-23 18:55:34 -0700246 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900247 onStandby(STANDBY_SHUTDOWN);
248 }
249 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900250 }
251 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900252
Yuncheol Heo38db6292014-07-01 14:15:14 +0900253 }
254
Jungshik Jang0792d372014-04-23 17:57:26 +0900255 // A thread to handle synchronous IO of CEC and MHL control service.
256 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
257 // and sparse call it shares a thread to handle IO operations.
258 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
259
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900260 // Used to synchronize the access to the service.
261 private final Object mLock = new Object();
262
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900263 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
264 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900265
Madhava Srinivasan154e0192019-08-26 22:15:31 +0200266 // List of records for HDMI control status change listener for death monitoring.
267 @GuardedBy("mLock")
268 private final ArrayList<HdmiControlStatusChangeListenerRecord>
269 mHdmiControlStatusChangeListenerRecords = new ArrayList<>();
270
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900271 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900272 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900273 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
274 new ArrayList<>();
275
Jungshik Jangf4249322014-08-21 14:17:05 +0900276 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900277 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900278 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
279 new ArrayList<>();
280
Jungshik Jangf4249322014-08-21 14:17:05 +0900281 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900282 @GuardedBy("mLock")
283 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
284 new ArrayList<>();
285
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900286 @GuardedBy("mLock")
287 private InputChangeListenerRecord mInputChangeListenerRecord;
288
Jungshik Jangb6591b82014-07-23 16:10:23 +0900289 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900290 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900291
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900292 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
293 // handling will be disabled and no request will be handled.
294 @GuardedBy("mLock")
295 private boolean mHdmiControlEnabled;
296
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900297 // Set to true while the service is in normal mode. While set to false, no input change is
298 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
299 // system upgrade, etc., a.k.a. "prohibit mode".
300 @GuardedBy("mLock")
301 private boolean mProhibitMode;
302
Jungshik Jangea67c182014-06-19 22:19:20 +0900303 // List of records for system audio mode change to handle the the caller killed in action.
304 private final ArrayList<SystemAudioModeChangeListenerRecord>
305 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
306
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900307 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900308 private final Handler mHandler = new Handler();
309
Jinsuk Kim50084862014-08-07 13:11:40 +0900310 private final SettingsObserver mSettingsObserver;
311
Jungshik Jangf4249322014-08-21 14:17:05 +0900312 private final HdmiControlBroadcastReceiver
313 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
314
Jungshik Jang0792d372014-04-23 17:57:26 +0900315 @Nullable
Amy702b4ad2019-08-21 16:24:03 -0700316 // Save callback when the device is still under logcial address allocation
317 // Invoke once new local device is ready.
318 private IHdmiControlCallback mDisplayStatusCallback = null;
319
320 @Nullable
Amyf5a866e2019-09-17 16:31:48 -0700321 // Save callback when the device is still under logcial address allocation
322 // Invoke once new local device is ready.
323 private IHdmiControlCallback mOtpCallbackPendingAddressAllocation = null;
324
325 @Nullable
Jungshik Jang0792d372014-04-23 17:57:26 +0900326 private HdmiCecController mCecController;
327
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900328 // HDMI port information. Stored in the unmodifiable list to keep the static information
329 // from being modified.
Amy6a58a342019-05-16 18:24:24 -0700330 // This variable is null if the current device does not have hdmi input.
331 @GuardedBy("mLock")
332 private List<HdmiPortInfo> mPortInfo = null;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900333
Jinsuk Kim2b152012014-07-25 08:22:26 +0900334 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900335 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900336
337 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900338 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900339
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900340 // Map from port ID to HdmiDeviceInfo.
341 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
342
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900343 private HdmiCecMessageValidator mMessageValidator;
344
Yuncheol Heo38db6292014-07-01 14:15:14 +0900345 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900346 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900347
348 @ServiceThreadOnly
Nick Chalko9bed7172020-02-11 13:09:03 -0800349 private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
Terry Heo1ca0a432014-08-18 10:30:32 +0900350
351 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900352 private boolean mStandbyMessageReceived = false;
353
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900354 @ServiceThreadOnly
355 private boolean mWakeUpMessageReceived = false;
356
Jungshik Jang867b4e02014-08-12 13:41:30 +0900357 @ServiceThreadOnly
358 private int mActivePortId = Constants.INVALID_PORT_ID;
359
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900360 // Set to true while the input change by MHL is allowed.
361 @GuardedBy("mLock")
362 private boolean mMhlInputChangeEnabled;
363
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900364 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900365 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900366 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
367 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900368
369 @GuardedBy("mLock")
370 private List<HdmiDeviceInfo> mMhlDevices;
371
372 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900373 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900374
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900375 @Nullable
376 private TvInputManager mTvInputManager;
377
Jinsuk Kime26d8332015-01-09 08:55:41 +0900378 @Nullable
379 private PowerManager mPowerManager;
380
Amy1d0b1372018-05-24 14:36:25 -0700381 @Nullable
382 private Looper mIoLooper;
383
Amyd58d0aa2018-10-18 14:08:57 -0700384 // Thread safe physical address
385 @GuardedBy("mLock")
386 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
387
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900388 // Last input port before switching to the MHL port. Should switch back to this port
389 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900390 // Gets invalidated if we go to other port/input.
391 @ServiceThreadOnly
392 private int mLastInputMhl = Constants.INVALID_PORT_ID;
393
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900394 // Set to true if the logical address allocation is completed.
395 private boolean mAddressAllocated = false;
396
397 // Buffer for processing the incoming cec messages while allocating logical addresses.
398 private final class CecMessageBuffer {
399 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
400
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900401 public boolean bufferMessage(HdmiCecMessage message) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900402 switch (message.getOpcode()) {
403 case Constants.MESSAGE_ACTIVE_SOURCE:
404 bufferActiveSource(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900405 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900406 case Constants.MESSAGE_IMAGE_VIEW_ON:
407 case Constants.MESSAGE_TEXT_VIEW_ON:
408 bufferImageOrTextViewOn(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900409 return true;
Jinping Wange55d3d42019-04-16 17:20:51 +0800410 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
411 bufferSystemAudioModeRequest(message);
412 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900413 // Add here if new message that needs to buffer
414 default:
415 // Do not need to buffer messages other than above
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900416 return false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900417 }
418 }
419
420 public void processMessages() {
421 for (final HdmiCecMessage message : mBuffer) {
422 runOnServiceThread(new Runnable() {
423 @Override
424 public void run() {
425 handleCecCommand(message);
426 }
427 });
428 }
429 mBuffer.clear();
430 }
431
432 private void bufferActiveSource(HdmiCecMessage message) {
433 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
434 mBuffer.add(message);
435 }
436 }
437
438 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
439 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
440 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
441 mBuffer.add(message);
442 }
443 }
444
Jinping Wange55d3d42019-04-16 17:20:51 +0800445 private void bufferSystemAudioModeRequest(HdmiCecMessage message) {
446 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST)) {
447 mBuffer.add(message);
448 }
449 }
450
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900451 // Returns true if the message is replaced
452 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
453 for (int i = 0; i < mBuffer.size(); i++) {
454 HdmiCecMessage bufferedMessage = mBuffer.get(i);
455 if (bufferedMessage.getOpcode() == opcode) {
456 mBuffer.set(i, message);
457 return true;
458 }
459 }
460 return false;
461 }
462 }
463
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900464 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
465
466 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900467
Jungshik Jang0792d372014-04-23 17:57:26 +0900468 public HdmiControlService(Context context) {
469 super(context);
Nick Chalko4797b7c2019-09-25 12:07:23 -0700470 List<Integer> deviceTypes = HdmiProperties.device_type();
471 if (deviceTypes.contains(null)) {
472 Slog.w(TAG, "Error parsing ro.hdmi.device.type: " + SystemProperties.get(
473 "ro.hdmi.device_type"));
474 deviceTypes = deviceTypes.stream().filter(Objects::nonNull).collect(
475 Collectors.toList());
476 }
477 mLocalDevices = deviceTypes;
Jinsuk Kim50084862014-08-07 13:11:40 +0900478 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900479 }
480
Amy8027c942018-09-18 10:23:20 -0700481 protected static List<Integer> getIntList(String string) {
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900482 ArrayList<Integer> list = new ArrayList<>();
483 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
484 splitter.setString(string);
485 for (String item : splitter) {
486 try {
487 list.add(Integer.parseInt(item));
488 } catch (NumberFormatException e) {
489 Slog.w(TAG, "Can't parseInt: " + item);
490 }
491 }
492 return Collections.unmodifiableList(list);
493 }
494
Jungshik Jang0792d372014-04-23 17:57:26 +0900495 @Override
496 public void onStart() {
Amy1d0b1372018-05-24 14:36:25 -0700497 if (mIoLooper == null) {
498 mIoThread.start();
499 mIoLooper = mIoThread.getLooper();
500 }
Robert Horvath04848a32019-11-29 16:14:42 +0100501 mPowerStatus = getInitialPowerStatus();
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900502 mProhibitMode = false;
503 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Marvin Raminda665a62020-03-16 14:17:58 +0100504 mHdmiCecVolumeControlEnabled = readBooleanSetting(
505 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900506 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900507
Amy1d0b1372018-05-24 14:36:25 -0700508 if (mCecController == null) {
509 mCecController = HdmiCecController.create(this);
510 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900511 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +0900512 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900513 initializeCec(INITIATED_BY_BOOT_UP);
Amy718e41e2018-08-17 17:23:37 -0700514 } else {
515 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Jungshik Janga9f10622014-07-11 15:36:39 +0900516 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900517 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900518 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900519 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900520 }
Amy4e7ff1a2018-06-07 16:24:31 -0700521 if (mMhlController == null) {
522 mMhlController = HdmiMhlControllerStub.create(this);
523 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900524 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900525 Slog.i(TAG, "Device does not support MHL-control.");
526 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900527 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900528
529 initPortInfo();
Amy1d0b1372018-05-24 14:36:25 -0700530 if (mMessageValidator == null) {
531 mMessageValidator = new HdmiCecMessageValidator(this);
532 }
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900533 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900534
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900535 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900536 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900537 IntentFilter filter = new IntentFilter();
538 filter.addAction(Intent.ACTION_SCREEN_OFF);
539 filter.addAction(Intent.ACTION_SCREEN_ON);
Rob McConnell30595562016-03-11 11:03:00 +0000540 filter.addAction(Intent.ACTION_SHUTDOWN);
Terry Heo1ca0a432014-08-18 10:30:32 +0900541 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
542 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900543
544 // Register ContentObserver to monitor the settings change.
545 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900546 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900547 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900548 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900549
Robert Horvath04848a32019-11-29 16:14:42 +0100550 private void bootCompleted() {
551 // on boot, if device is interactive, set HDMI CEC state as powered on as well
552 if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) {
553 onWakeUp();
554 }
555 }
556
557 /**
558 * Returns the initial power status used when the HdmiControlService starts.
559 */
560 @VisibleForTesting
561 int getInitialPowerStatus() {
562 // The initial power status is POWER_STATUS_TRANSIENT_TO_STANDBY.
563 // Once boot completes the service transitions to POWER_STATUS_ON if the device is
564 // interactive.
565 // Quiescent boot is a special boot mode, in which the screen stays off during boot
566 // and the device goes to sleep after boot has finished.
567 // We don't transition to POWER_STATUS_ON initially, as we might be booting in quiescent
568 // mode, during which we don't want to appear powered on to avoid being made active source.
569 return HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
570 }
571
Amy1d0b1372018-05-24 14:36:25 -0700572 @VisibleForTesting
573 void setCecController(HdmiCecController cecController) {
574 mCecController = cecController;
575 }
576
Amy4e7ff1a2018-06-07 16:24:31 -0700577 @VisibleForTesting
578 void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
579 mMhlController = hdmiMhlController;
580 }
581
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900582 @Override
583 public void onBootPhase(int phase) {
584 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
585 mTvInputManager = (TvInputManager) getContext().getSystemService(
586 Context.TV_INPUT_SERVICE);
Robert Horvath04848a32019-11-29 16:14:42 +0100587 mPowerManager = getContext().getSystemService(PowerManager.class);
588 } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
589 runOnServiceThread(this::bootCompleted);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900590 }
591 }
592
593 TvInputManager getTvInputManager() {
594 return mTvInputManager;
595 }
596
597 void registerTvInputCallback(TvInputCallback callback) {
598 if (mTvInputManager == null) return;
599 mTvInputManager.registerCallback(callback, mHandler);
600 }
601
602 void unregisterTvInputCallback(TvInputCallback callback) {
603 if (mTvInputManager == null) return;
604 mTvInputManager.unregisterCallback(callback);
605 }
606
Jinsuk Kime26d8332015-01-09 08:55:41 +0900607 PowerManager getPowerManager() {
608 return mPowerManager;
609 }
610
Yuncheol Heo25c20292014-07-31 17:59:39 +0900611 /**
612 * Called when the initialization of local devices is complete.
613 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900614 private void onInitializeCecComplete(int initiatedBy) {
Robert Horvath04848a32019-11-29 16:14:42 +0100615 updatePowerStatusOnInitializeCecComplete();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900616 mWakeUpMessageReceived = false;
617
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900618 if (isTvDeviceEnabled()) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900619 mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
Yuncheol Heo0608b932014-10-13 16:39:18 +0900620 }
621 int reason = -1;
622 switch (initiatedBy) {
623 case INITIATED_BY_BOOT_UP:
624 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
625 break;
626 case INITIATED_BY_ENABLE_CEC:
627 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
628 break;
629 case INITIATED_BY_SCREEN_ON:
630 case INITIATED_BY_WAKE_UP_MESSAGE:
631 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
632 break;
633 }
634 if (reason != -1) {
635 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Madhava Srinivasan154e0192019-08-26 22:15:31 +0200636 announceHdmiControlStatusChange(true);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900637 }
638 }
639
Robert Horvath04848a32019-11-29 16:14:42 +0100640 /**
641 * Updates the power status once the initialization of local devices is complete.
642 */
643 private void updatePowerStatusOnInitializeCecComplete() {
644 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
645 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
646 } else if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
647 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
648 }
649 }
650
Jinsuk Kim50084862014-08-07 13:11:40 +0900651 private void registerContentObserver() {
652 ContentResolver resolver = getContext().getContentResolver();
653 String[] settings = new String[] {
654 Global.HDMI_CONTROL_ENABLED,
Marvin Raminda665a62020-03-16 14:17:58 +0100655 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
Jinsuk Kim50084862014-08-07 13:11:40 +0900656 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
657 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900658 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Jinsuk Kim50084862014-08-07 13:11:40 +0900659 Global.MHL_INPUT_SWITCHING_ENABLED,
Amy0c2e29f2018-10-23 12:17:52 -0700660 Global.MHL_POWER_CHARGE_ENABLED,
Amy44d7af22019-02-21 13:27:26 -0800661 Global.HDMI_CEC_SWITCH_ENABLED,
662 Global.DEVICE_NAME
Jinsuk Kim50084862014-08-07 13:11:40 +0900663 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900664 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900665 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
666 UserHandle.USER_ALL);
667 }
668 }
669
670 private class SettingsObserver extends ContentObserver {
671 public SettingsObserver(Handler handler) {
672 super(handler);
673 }
674
Jungshik Jangf67113f2014-08-22 16:27:19 +0900675 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900676 @Override
677 public void onChange(boolean selfChange, Uri uri) {
678 String option = uri.getLastPathSegment();
679 boolean enabled = readBooleanSetting(option, true);
680 switch (option) {
681 case Global.HDMI_CONTROL_ENABLED:
682 setControlEnabled(enabled);
683 break;
Marvin Raminda665a62020-03-16 14:17:58 +0100684 case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
685 setHdmiCecVolumeControlEnabled(enabled);
686 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900687 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900688 if (isTvDeviceEnabled()) {
689 tv().setAutoWakeup(enabled);
690 }
Donghyun Chobc6e3722016-11-04 05:25:52 +0900691 setCecOption(OptionKey.WAKEUP, enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900692 break;
693 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900694 for (int type : mLocalDevices) {
695 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
Terry Heodd371ec2015-12-10 15:31:05 +0900696 if (localDevice != null) {
697 localDevice.setAutoDeviceOff(enabled);
698 }
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900699 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900700 // No need to propagate to HAL.
701 break;
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900702 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
703 if (isTvDeviceEnabled()) {
704 tv().setSystemAudioControlFeatureEnabled(enabled);
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900705 }
Amy79db52f2018-10-23 12:45:17 -0700706 if (isAudioSystemDevice()) {
Amy8ba147c2018-11-16 10:59:18 -0800707 if (audioSystem() == null) {
708 Slog.e(TAG, "Audio System device has not registered yet."
709 + " Can't turn system audio mode on.");
710 break;
711 }
Amy79db52f2018-10-23 12:45:17 -0700712 audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
713 }
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900714 break;
Amy0c2e29f2018-10-23 12:17:52 -0700715 case Global.HDMI_CEC_SWITCH_ENABLED:
716 if (isAudioSystemDevice()) {
Amy7f69d672018-11-09 15:46:47 -0800717 if (audioSystem() == null) {
718 Slog.w(TAG, "Switch device has not registered yet."
719 + " Can't turn routing on.");
720 break;
721 }
Amy0c2e29f2018-10-23 12:17:52 -0700722 audioSystem().setRoutingControlFeatureEnables(enabled);
723 }
724 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900725 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900726 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900727 break;
728 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900729 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900730 break;
Amy44d7af22019-02-21 13:27:26 -0800731 case Global.DEVICE_NAME:
732 String deviceName = readStringSetting(option, Build.MODEL);
733 setDisplayName(deviceName);
734 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900735 }
736 }
737 }
738
739 private static int toInt(boolean enabled) {
740 return enabled ? ENABLED : DISABLED;
741 }
742
Amyaa8ae682018-12-12 16:11:50 -0800743 @VisibleForTesting
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900744 boolean readBooleanSetting(String key, boolean defVal) {
745 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900746 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900747 }
748
749 void writeBooleanSetting(String key, boolean value) {
750 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900751 Global.putInt(cr, key, toInt(value));
752 }
753
Amy59c06c12019-01-18 15:35:15 -0800754 void writeStringSystemProperty(String key, String value) {
755 SystemProperties.set(key, value);
756 }
757
758 @VisibleForTesting
759 boolean readBooleanSystemProperty(String key, boolean defVal) {
760 return SystemProperties.getBoolean(key, defVal);
Amyaa8ae682018-12-12 16:11:50 -0800761 }
762
Amy44d7af22019-02-21 13:27:26 -0800763 String readStringSetting(String key, String defVal) {
764 ContentResolver cr = getContext().getContentResolver();
765 String content = Global.getString(cr, key);
766 if (TextUtils.isEmpty(content)) {
767 return defVal;
768 }
769 return content;
770 }
771
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900772 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900773 mAddressAllocated = false;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900774 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Nick Chalko9bed7172020-02-11 13:09:03 -0800775 mCecController.setLanguage(mMenuLanguage);
Yuncheol Heob5021862014-09-02 10:36:04 +0900776 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900777 }
778
Jungshik Janga5b74142014-06-23 18:03:10 +0900779 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900780 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900781 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900782 // A container for [Device type, Local device info].
783 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900784 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -0700785 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
786 && isHdmiCecNeverClaimPlaybackLogicAddr) {
787 continue;
788 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900789 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
790 if (localDevice == null) {
791 localDevice = HdmiCecLocalDevice.create(this, type);
792 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900793 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900794 localDevices.add(localDevice);
795 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900796 // It's now safe to flush existing local devices from mCecController since they were
797 // already moved to 'localDevices'.
798 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900799 allocateLogicalAddress(localDevices, initiatedBy);
800 }
801
802 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700803 @VisibleForTesting
804 protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
Yuncheol Heob5021862014-09-02 10:36:04 +0900805 final int initiatedBy) {
806 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900807 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900808 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
809 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900810 mAddressAllocated = allocatingDevices.isEmpty();
811
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900812 // For TV device, select request can be invoked while address allocation or device
813 // discovery is in progress. Initialize the request here at the start of allocation,
814 // and process the collected requests later when the allocation and device discovery
815 // is all completed.
816 mSelectRequestBuffer.clear();
817
Yuncheol Heob5021862014-09-02 10:36:04 +0900818 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
819 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900820 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
821 @Override
822 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900823 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900824 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
825 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900826 // Set POWER_STATUS_ON to all local devices because they share lifetime
827 // with system.
828 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
829 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900830 localDevice.setDeviceInfo(deviceInfo);
831 mCecController.addLocalDevice(deviceType, localDevice);
832 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900833 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900834 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900835
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900836 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900837 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900838 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900839 if (initiatedBy != INITIATED_BY_HOTPLUG) {
840 // In case of the hotplug we don't call onInitializeCecComplete()
841 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900842 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900843 }
844 notifyAddressAllocated(allocatedDevices, initiatedBy);
Amyf5a866e2019-09-17 16:31:48 -0700845 // Reinvoke the saved display status callback once the local device is ready.
846 if (mDisplayStatusCallback != null) {
847 queryDisplayStatus(mDisplayStatusCallback);
848 mDisplayStatusCallback = null;
849 }
850 if (mOtpCallbackPendingAddressAllocation != null) {
851 oneTouchPlay(mOtpCallbackPendingAddressAllocation);
852 mOtpCallbackPendingAddressAllocation = null;
853 }
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900854 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900855 }
856 }
857 });
858 }
859 }
860
Jungshik Janga5b74142014-06-23 18:03:10 +0900861 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900862 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900863 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900864 for (HdmiCecLocalDevice device : devices) {
865 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900866 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900867 }
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900868 if (isTvDeviceEnabled()) {
869 tv().setSelectRequestBuffer(mSelectRequestBuffer);
870 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900871 }
872
Donghyun Chofc462b92016-05-13 21:06:02 +0900873 boolean isAddressAllocated() {
874 return mAddressAllocated;
875 }
876
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900877 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
878 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900879 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700880 @VisibleForTesting
881 protected void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900882 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900883 HdmiPortInfo[] cecPortInfo = null;
884
Amyd58d0aa2018-10-18 14:08:57 -0700885 synchronized (mLock) {
886 mPhysicalAddress = getPhysicalAddress();
887 }
888
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900889 // CEC HAL provides majority of the info while MHL does only MHL support flag for
890 // each port. Return empty array if CEC HAL didn't provide the info.
891 if (mCecController != null) {
892 cecPortInfo = mCecController.getPortInfos();
893 }
894 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900895 return;
896 }
897
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900898 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
899 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900900 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900901 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900902 portIdMap.put(info.getAddress(), info.getId());
903 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900904 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900905 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900906 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
907 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900908 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900909
Amya00e1182018-10-17 16:45:03 -0700910 if (mMhlController == null) {
911 return;
912 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900913 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
914 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
915 for (HdmiPortInfo info : mhlPortInfo) {
916 if (info.isMhlSupported()) {
917 mhlSupportedPorts.add(info.getId());
918 }
919 }
920
921 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
922 // cec port info if we do not have have port that supports MHL.
923 if (mhlSupportedPorts.isEmpty()) {
Amy6a58a342019-05-16 18:24:24 -0700924 setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900925 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900926 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900927 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
928 for (HdmiPortInfo info : cecPortInfo) {
929 if (mhlSupportedPorts.contains(info.getId())) {
930 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
931 info.isCecSupported(), true, info.isArcSupported()));
932 } else {
933 result.add(info);
934 }
935 }
Amy6a58a342019-05-16 18:24:24 -0700936 setPortInfo(Collections.unmodifiableList(result));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900937 }
938
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900939 List<HdmiPortInfo> getPortInfo() {
Amy6a58a342019-05-16 18:24:24 -0700940 synchronized (mLock) {
941 return mPortInfo;
942 }
943 }
944
945 void setPortInfo(List<HdmiPortInfo> portInfo) {
946 synchronized (mLock) {
947 mPortInfo = portInfo;
948 }
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900949 }
950
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900951 /**
952 * Returns HDMI port information for the given port id.
953 *
954 * @param portId HDMI port id
955 * @return {@link HdmiPortInfo} for the given port
956 */
957 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900958 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900959 }
960
Jungshik Jange9c77c82014-04-24 20:30:09 +0900961 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900962 * Returns the routing path (physical address) of the HDMI port for the given
963 * port id.
964 */
965 int portIdToPath(int portId) {
966 HdmiPortInfo portInfo = getPortInfo(portId);
967 if (portInfo == null) {
968 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900969 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900970 }
971 return portInfo.getAddress();
972 }
973
974 /**
Amya00e1182018-10-17 16:45:03 -0700975 * Returns the id of HDMI port located at the current device that runs this method.
976 *
977 * For TV with physical address 0x0000, target device 0x1120, we want port physical address
978 * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
979 * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
980 *
981 * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
982 *
983 * @param path the target device's physical address.
984 * @return the id of the port that the target device eventually connects to
985 * on the current device.
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900986 */
987 int pathToPortId(int path) {
Amya00e1182018-10-17 16:45:03 -0700988 int mask = 0xF000;
989 int finalMask = 0xF000;
Amyf7782b32018-11-16 11:21:03 -0800990 int physicalAddress;
991 synchronized (mLock) {
992 physicalAddress = mPhysicalAddress;
993 }
Amy3eaa85f2018-10-18 14:17:47 -0700994 int maskedAddress = physicalAddress;
995
996 while (maskedAddress != 0) {
997 maskedAddress = physicalAddress & mask;
Amya00e1182018-10-17 16:45:03 -0700998 finalMask |= mask;
Amy3eaa85f2018-10-18 14:17:47 -0700999 mask >>= 4;
Amya00e1182018-10-17 16:45:03 -07001000 }
Amy3eaa85f2018-10-18 14:17:47 -07001001
Amya00e1182018-10-17 16:45:03 -07001002 int portAddress = path & finalMask;
Jinsuk Kim2b152012014-07-25 08:22:26 +09001003 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +09001004 }
1005
Jinsuk Kim09ffc842014-07-11 17:04:32 +09001006 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +09001007 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +09001008 }
1009
Jinsuk Kim401e3de2014-06-14 07:47:39 +09001010 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +09001011 * Returns {@link Looper} for IO operation.
1012 *
1013 * <p>Declared as package-private.
1014 */
Amy1d0b1372018-05-24 14:36:25 -07001015 @Nullable
Jungshik Jange9c77c82014-04-24 20:30:09 +09001016 Looper getIoLooper() {
Amy1d0b1372018-05-24 14:36:25 -07001017 return mIoLooper;
1018 }
1019
1020 @VisibleForTesting
1021 void setIoLooper(Looper ioLooper) {
1022 mIoLooper = ioLooper;
1023 }
1024
1025 @VisibleForTesting
1026 void setMessageValidator(HdmiCecMessageValidator messageValidator) {
1027 mMessageValidator = messageValidator;
Jungshik Jange9c77c82014-04-24 20:30:09 +09001028 }
1029
1030 /**
1031 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
1032 * for tasks that are running on main service thread.
1033 *
1034 * <p>Declared as package-private.
1035 */
1036 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +09001037 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +09001038 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001039
1040 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +09001041 * Returns physical address of the device.
1042 */
1043 int getPhysicalAddress() {
1044 return mCecController.getPhysicalAddress();
1045 }
1046
1047 /**
1048 * Returns vendor id of CEC service.
1049 */
1050 int getVendorId() {
1051 return mCecController.getVendorId();
1052 }
1053
Jungshik Janga5b74142014-06-23 18:03:10 +09001054 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001055 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001056 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001057 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001058 }
1059
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +09001060 @ServiceThreadOnly
1061 HdmiDeviceInfo getDeviceInfoByPort(int port) {
1062 assertRunOnServiceThread();
1063 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
1064 if (info != null) {
1065 return info.getInfo();
1066 }
1067 return null;
1068 }
1069
Jungshik Jang3ee65722014-06-03 16:22:30 +09001070 /**
Jungshik Jang092b4452014-06-11 15:19:17 +09001071 * Returns version of CEC.
1072 */
1073 int getCecVersion() {
1074 return mCecController.getVersion();
1075 }
1076
1077 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +09001078 * Whether a device of the specified physical address is connected to ARC enabled port.
1079 */
1080 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +09001081 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +09001082 if (portId != Constants.INVALID_PORT_ID) {
1083 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +09001084 }
1085 return false;
1086 }
1087
Jinsuk Kim7b0cf642015-04-14 09:43:45 +09001088 @ServiceThreadOnly
1089 boolean isConnected(int portId) {
1090 assertRunOnServiceThread();
1091 return mCecController.isConnected(portId);
1092 }
1093
Jungshik Jang79c58a42014-06-16 16:45:36 +09001094 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +09001095 mHandler.post(runnable);
1096 }
1097
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001098 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
1099 mHandler.postAtFrontOfQueue(runnable);
1100 }
1101
1102 private void assertRunOnServiceThread() {
1103 if (Looper.myLooper() != mHandler.getLooper()) {
1104 throw new IllegalStateException("Should run on service thread.");
1105 }
1106 }
1107
Jungshik Jang67ea5212014-05-15 14:05:24 +09001108 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001109 * Transmit a CEC command to CEC bus.
1110 *
1111 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +09001112 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001113 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001114 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +09001115 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001116 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +09001117 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001118 mCecController.sendCommand(command, callback);
1119 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001120 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001121 if (callback != null) {
Donghyun Chobc6e3722016-11-04 05:25:52 +09001122 callback.onSendCompleted(SendMessageResult.FAIL);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001123 }
1124 }
Jungshik Jangd643f762014-05-22 19:28:09 +09001125 }
1126
Jungshik Janga5b74142014-06-23 18:03:10 +09001127 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +09001128 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001129 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001130 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001131 }
1132
Yuncheol Heo6aae6522014-08-05 14:48:37 +09001133 /**
1134 * Send <Feature Abort> command on the given CEC message if possible.
1135 * If the aborted message is invalid, then it wont send the message.
1136 * @param command original command to be aborted
1137 * @param reason reason of feature abort
1138 */
1139 @ServiceThreadOnly
1140 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
1141 assertRunOnServiceThread();
1142 mCecController.maySendFeatureAbortCommand(command, reason);
1143 }
1144
Jungshik Janga5b74142014-06-23 18:03:10 +09001145 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001146 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001147 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +09001148 int errorCode = mMessageValidator.isValid(message);
1149 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +09001150 // We'll not response on the messages with the invalid source or destination
1151 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +09001152 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
1153 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
1154 }
1155 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +09001156 }
Kyeongkab.Nama15539e2018-09-14 13:55:55 +09001157
1158 if (dispatchMessageToLocalDevice(message)) {
1159 return true;
1160 }
1161
1162 return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
Jungshik Jang092b4452014-06-11 15:19:17 +09001163 }
1164
Donghyun Chobc6e3722016-11-04 05:25:52 +09001165 void enableAudioReturnChannel(int portId, boolean enabled) {
1166 mCecController.enableAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001167 }
1168
Jungshik Janga5b74142014-06-23 18:03:10 +09001169 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +09001170 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001171 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +09001172 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001173 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001174 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +09001175 return true;
1176 }
1177 }
Jungshik Jang60cffce2014-06-12 18:03:04 +09001178
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001179 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001180 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +09001181 }
Jungshik Jang092b4452014-06-11 15:19:17 +09001182 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001183 }
1184
Jungshik Jang67ea5212014-05-15 14:05:24 +09001185 /**
1186 * Called when a new hotplug event is issued.
1187 *
Jinsuk Kimed086452014-08-18 15:01:53 +09001188 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +09001189 * @param connected whether to be plugged in or not
1190 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001191 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +09001192 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +09001193 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +09001194
Amy59176da2018-10-12 16:30:54 -07001195 if (connected && !isTvDevice()
1196 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
1197 if (isSwitchDevice()) {
Amy066db152018-10-04 09:54:51 -07001198 initPortInfo();
1199 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
1200 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001201 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1202 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -07001203 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
1204 && isHdmiCecNeverClaimPlaybackLogicAddr) {
1205 continue;
1206 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001207 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
1208 if (localDevice == null) {
1209 localDevice = HdmiCecLocalDevice.create(this, type);
1210 localDevice.init();
1211 }
1212 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +09001213 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001214 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +09001215 }
Yuncheol Heob5021862014-09-02 10:36:04 +09001216
Jungshik Jang79c58a42014-06-16 16:45:36 +09001217 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +09001218 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001219 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001220 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +09001221 }
1222
Jungshik Jang02bb4262014-05-23 16:48:31 +09001223 /**
1224 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
1225 * devices.
1226 *
1227 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +09001228 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001229 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +09001230 * @param retryCount the number of retry used to send polling message to remote devices
Jakub Pawlowskib0b3a642018-12-05 10:55:57 +01001231 * @throws IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +09001232 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001233 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +09001234 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
1235 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001236 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +09001237 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
1238 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001239 }
1240
1241 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001242 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001243 if (strategy == 0) {
1244 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
1245 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001246 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001247 if (iterationStrategy == 0) {
1248 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
1249 }
1250 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +09001251 }
1252
Jungshik Jang60cffce2014-06-12 18:03:04 +09001253 List<HdmiCecLocalDevice> getAllLocalDevices() {
1254 assertRunOnServiceThread();
1255 return mCecController.getLocalDeviceList();
1256 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001257
Amyc3371612019-05-29 17:41:51 -07001258 /**
1259 * Check if a logical address is conflict with the current device's. Reallocate the logical
1260 * address of the current device if there is conflict.
1261 *
1262 * Android HDMI CEC 1.4 is handling logical address allocation in the framework side. This could
1263 * introduce delay between the logical address allocation and notifying the driver that the
1264 * address is occupied. Adding this check to avoid such case.
1265 *
1266 * @param logicalAddress logical address of the remote device that might have the same logical
1267 * address as the current device.
1268 */
1269 protected void checkLogicalAddressConflictAndReallocate(int logicalAddress) {
1270 for (HdmiCecLocalDevice device : getAllLocalDevices()) {
1271 if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) {
1272 HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo());
1273 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1274 localDevices.add(device);
1275 allocateLogicalAddress(localDevices, HdmiControlService.INITIATED_BY_HOTPLUG);
1276 return;
1277 }
1278 }
1279 }
1280
Jungshik Jang79c58a42014-06-16 16:45:36 +09001281 Object getServiceLock() {
1282 return mLock;
1283 }
1284
1285 void setAudioStatus(boolean mute, int volume) {
Marvin Raminda665a62020-03-16 14:17:58 +01001286 if (!isTvDeviceEnabled()
1287 || !tv().isSystemAudioActivated()
1288 || !isHdmiCecVolumeControlEnabled()) {
Donghyun Cho65618452016-12-23 18:30:37 +09001289 return;
1290 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001291 AudioManager audioManager = getAudioManager();
1292 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
1293 if (mute) {
1294 if (!muted) {
1295 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
1296 }
1297 } else {
1298 if (muted) {
1299 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
1300 }
1301 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
1302 // volume change notification back to hdmi control service.
Shuichi.Noguchifbb50bc2017-12-06 11:12:33 +09001303 int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
1304 if (0 <= volume && volume <= 100) {
1305 Slog.i(TAG, "volume: " + volume);
1306 flag |= AudioManager.FLAG_SHOW_UI;
1307 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
1308 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001309 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001310 }
1311
Jungshik Jangea67c182014-06-19 22:19:20 +09001312 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001313 synchronized (mLock) {
1314 for (SystemAudioModeChangeListenerRecord record :
1315 mSystemAudioModeChangeListenerRecords) {
1316 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1317 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001318 }
1319 }
1320
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001321 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Amy44d7af22019-02-21 13:27:26 -08001322 String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL);
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001323 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +09001324 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
Amy0361dd72018-11-07 16:54:25 -08001325 getVendorId(), displayName, powerStatus);
Jungshik Jang3ee65722014-06-03 16:22:30 +09001326 }
1327
Amy44d7af22019-02-21 13:27:26 -08001328 // Set the display name in HdmiDeviceInfo of the current devices to content provided by
1329 // Global.DEVICE_NAME. Only set and broadcast if the new name is different.
1330 private void setDisplayName(String newDisplayName) {
1331 for (HdmiCecLocalDevice device : getAllLocalDevices()) {
1332 HdmiDeviceInfo deviceInfo = device.getDeviceInfo();
1333 if (deviceInfo.getDisplayName().equals(newDisplayName)) {
1334 continue;
1335 }
1336 device.setDeviceInfo(new HdmiDeviceInfo(
1337 deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(),
1338 deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(),
1339 newDisplayName, deviceInfo.getDevicePowerStatus()));
1340 sendCecCommand(HdmiCecMessageBuilder.buildSetOsdNameCommand(
1341 device.mAddress, Constants.ADDR_TV, newDisplayName));
1342 }
1343 }
1344
Jungshik Jang7df52862014-08-11 14:35:27 +09001345 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +09001346 void handleMhlHotplugEvent(int portId, boolean connected) {
1347 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001348 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +09001349 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001350 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1351 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +09001352 if (oldDevice != null) {
1353 oldDevice.onDeviceRemoved();
1354 Slog.i(TAG, "Old device of port " + portId + " is removed");
1355 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001356 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1357 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001358 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001359 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001360 if (device != null) {
1361 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001362 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1363 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001364 } else {
1365 Slog.w(TAG, "No device to remove:[portId=" + portId);
1366 }
1367 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001368 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001369 }
1370
1371 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001372 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001373 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001374 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001375 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001376 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001377 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001378 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1379 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001380 }
1381 }
1382
1383 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001384 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001385 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001386 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001387 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001388 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001389 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001390 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001391 }
1392 }
1393
1394 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001395 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001396 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001397 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001398
Jungshik Jang7df52862014-08-11 14:35:27 +09001399 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001400 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001401 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001402 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001403 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1404 }
1405 }
1406
Jinsuk Kimed086452014-08-18 15:01:53 +09001407 @ServiceThreadOnly
1408 private void updateSafeMhlInput() {
1409 assertRunOnServiceThread();
1410 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001411 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001412 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001413 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001414 HdmiDeviceInfo info = device.getInfo();
1415 if (info != null) {
1416 if (inputs.isEmpty()) {
1417 inputs = new ArrayList<>();
1418 }
1419 inputs.add(device.getInfo());
1420 }
1421 }
1422 synchronized (mLock) {
1423 mMhlDevices = inputs;
1424 }
1425 }
1426
Andreas Gampea36dc622018-02-05 17:19:22 -08001427 @GuardedBy("mLock")
Jinsuk Kimed086452014-08-18 15:01:53 +09001428 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1429 return mMhlDevices;
1430 }
1431
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001432 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1433 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001434
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001435 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001436 mListener = listener;
1437 }
1438
1439 @Override
1440 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001441 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001442 }
1443 }
1444
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001445 // Record class that monitors the event of the caller of being killed. Used to clean up
1446 // the listener list and record list accordingly.
Madhava Srinivasan154e0192019-08-26 22:15:31 +02001447 private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient {
1448 private final IHdmiControlStatusChangeListener mListener;
1449
1450 HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) {
1451 mListener = listener;
1452 }
1453
1454 @Override
1455 public void binderDied() {
1456 synchronized (mLock) {
1457 mHdmiControlStatusChangeListenerRecords.remove(this);
1458 }
1459 }
1460
1461 @Override
1462 public boolean equals(Object obj) {
1463 if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false;
1464 if (obj == this) return true;
1465 HdmiControlStatusChangeListenerRecord other =
1466 (HdmiControlStatusChangeListenerRecord) obj;
1467 return other.mListener == this.mListener;
1468 }
1469
1470 @Override
1471 public int hashCode() {
1472 return mListener.hashCode();
1473 }
1474 }
1475
1476 // Record class that monitors the event of the caller of being killed. Used to clean up
1477 // the listener list and record list accordingly.
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001478 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1479 private final IHdmiHotplugEventListener mListener;
1480
1481 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1482 mListener = listener;
1483 }
1484
1485 @Override
1486 public void binderDied() {
1487 synchronized (mLock) {
1488 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001489 }
1490 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001491
1492 @Override
1493 public boolean equals(Object obj) {
1494 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1495 if (obj == this) return true;
1496 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1497 return other.mListener == this.mListener;
1498 }
1499
1500 @Override
1501 public int hashCode() {
1502 return mListener.hashCode();
1503 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001504 }
1505
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001506 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1507 private final IHdmiDeviceEventListener mListener;
1508
1509 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1510 mListener = listener;
1511 }
1512
1513 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001514 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001515 synchronized (mLock) {
1516 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001517 }
1518 }
1519 }
1520
Jungshik Jangea67c182014-06-19 22:19:20 +09001521 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001522 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001523
1524 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1525 mListener = listener;
1526 }
1527
1528 @Override
1529 public void binderDied() {
1530 synchronized (mLock) {
1531 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001532 }
1533 }
1534 }
1535
Jinsuk Kim119160a2014-07-07 18:48:10 +09001536 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1537 private final IHdmiVendorCommandListener mListener;
1538 private final int mDeviceType;
1539
1540 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1541 mListener = listener;
1542 mDeviceType = deviceType;
1543 }
1544
1545 @Override
1546 public void binderDied() {
1547 synchronized (mLock) {
1548 mVendorCommandListenerRecords.remove(this);
1549 }
1550 }
1551 }
1552
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001553 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001554 private final IHdmiRecordListener mListener;
1555
1556 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1557 mListener = listener;
1558 }
1559
Jungshik Jangb6591b82014-07-23 16:10:23 +09001560 @Override
1561 public void binderDied() {
1562 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001563 if (mRecordListenerRecord == this) {
1564 mRecordListenerRecord = null;
1565 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001566 }
1567 }
1568 }
1569
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001570 private void enforceAccessPermission() {
1571 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1572 }
1573
1574 private final class BinderService extends IHdmiControlService.Stub {
1575 @Override
1576 public int[] getSupportedTypes() {
1577 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001578 // mLocalDevices is an unmodifiable list - no lock necesary.
1579 int[] localDevices = new int[mLocalDevices.size()];
1580 for (int i = 0; i < localDevices.length; ++i) {
1581 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001582 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001583 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001584 }
1585
1586 @Override
Amy4d3551a2019-01-23 14:17:50 -08001587 @Nullable
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001588 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001589 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001590 HdmiCecLocalDeviceTv tv = tv();
1591 if (tv == null) {
Amy4d3551a2019-01-23 14:17:50 -08001592 if (isTvDevice()) {
1593 Slog.e(TAG, "Local tv device not available.");
1594 return null;
1595 }
Amy0361dd72018-11-07 16:54:25 -08001596 if (isPlaybackDevice()) {
1597 // if playback device itself is the active source,
1598 // return its own device info.
1599 if (playback() != null && playback().mIsActiveSource) {
1600 return playback().getDeviceInfo();
1601 }
1602 // Otherwise get the active source and look for it from the device list
Amy8a9eae72019-04-29 13:27:05 -07001603 ActiveSource activeSource = getLocalActiveSource();
1604 // If the physical address is not set yet, return null
1605 if (activeSource.physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) {
Amy0361dd72018-11-07 16:54:25 -08001606 return null;
1607 }
1608 if (audioSystem() != null) {
1609 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1610 for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
Amy8a9eae72019-04-29 13:27:05 -07001611 if (info.getPhysicalAddress() == activeSource.physicalAddress) {
Amy0361dd72018-11-07 16:54:25 -08001612 return info;
1613 }
1614 }
1615 }
1616 // If the device info is not in the list yet, return a device info with minimum
1617 // information from mActiveSource.
Amy8a9eae72019-04-29 13:27:05 -07001618 // If the Active Source has unregistered logical address, return with an
1619 // HdmiDeviceInfo built from physical address information only.
1620 return HdmiUtils.isValidAddress(activeSource.logicalAddress)
1621 ?
1622 new HdmiDeviceInfo(activeSource.logicalAddress,
1623 activeSource.physicalAddress,
1624 pathToPortId(activeSource.physicalAddress),
1625 HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0,
1626 HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress))
1627 :
1628 new HdmiDeviceInfo(activeSource.physicalAddress,
1629 pathToPortId(activeSource.physicalAddress));
1630
Amy0361dd72018-11-07 16:54:25 -08001631 }
Jinsuk Kim7e742062014-07-30 13:19:13 +09001632 return null;
1633 }
1634 ActiveSource activeSource = tv.getActiveSource();
1635 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001636 return new HdmiDeviceInfo(activeSource.logicalAddress,
1637 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1638 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001639 }
1640 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001641 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001642 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001643 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001644 }
1645 return null;
1646 }
1647
1648 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001649 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001650 enforceAccessPermission();
1651 runOnServiceThread(new Runnable() {
1652 @Override
1653 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001654 if (callback == null) {
1655 Slog.e(TAG, "Callback cannot be null");
1656 return;
1657 }
Amya9d5ca22019-06-06 18:14:28 -07001658 if (isPowerStandby()) {
1659 Slog.e(TAG, "Device is in standby. Not handling deviceSelect");
1660 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1661 return;
1662 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001663 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001664 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001665 if (!mAddressAllocated) {
1666 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1667 HdmiControlService.this, deviceId, callback));
1668 return;
1669 }
Amy4d3551a2019-01-23 14:17:50 -08001670 if (isTvDevice()) {
1671 Slog.e(TAG, "Local tv device not available");
1672 return;
1673 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001674 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001675 return;
1676 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001677 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001678 if (device != null) {
1679 if (device.getPortId() == tv.getActivePortId()) {
1680 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001681 return;
1682 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001683 // Upon selecting MHL device, we send RAP[Content On] to wake up
1684 // the connected mobile device, start routing control to switch ports.
1685 // callback is handled by MHL action.
1686 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001687 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001688 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001689 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001690 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001691 }
1692 });
1693 }
1694
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001695 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001696 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1697 enforceAccessPermission();
1698 runOnServiceThread(new Runnable() {
1699 @Override
1700 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001701 if (callback == null) {
1702 Slog.e(TAG, "Callback cannot be null");
1703 return;
1704 }
Amya9d5ca22019-06-06 18:14:28 -07001705 if (isPowerStandby()) {
1706 Slog.e(TAG, "Device is in standby. Not handling portSelect");
1707 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1708 return;
1709 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001710 HdmiCecLocalDeviceTv tv = tv();
shubangd932cb52018-09-14 17:55:16 -07001711 if (tv != null) {
1712 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001713 return;
1714 }
shubangd932cb52018-09-14 17:55:16 -07001715 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1716 if (audioSystem != null) {
1717 audioSystem.doManualPortSwitching(portId, callback);
1718 return;
1719 }
1720
1721 if (!mAddressAllocated) {
1722 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1723 HdmiControlService.this, portId, callback));
1724 return;
1725 }
1726 Slog.w(TAG, "Local device not available");
1727 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1728 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001729 }
1730 });
1731 }
1732
1733 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001734 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001735 enforceAccessPermission();
1736 runOnServiceThread(new Runnable() {
1737 @Override
1738 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001739 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001740 if (device != null) {
1741 device.sendKeyEvent(keyCode, isPressed);
1742 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001743 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001744 if (mCecController != null) {
1745 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1746 if (localDevice == null) {
Amybd8b4fa2019-01-30 11:27:49 -08001747 Slog.w(TAG, "Local device not available to send key event.");
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001748 return;
1749 }
1750 localDevice.sendKeyEvent(keyCode, isPressed);
1751 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001752 }
1753 });
1754 }
1755
1756 @Override
Amybd8b4fa2019-01-30 11:27:49 -08001757 public void sendVolumeKeyEvent(
1758 final int deviceType, final int keyCode, final boolean isPressed) {
1759 enforceAccessPermission();
1760 runOnServiceThread(new Runnable() {
1761 @Override
1762 public void run() {
1763 if (mCecController == null) {
1764 Slog.w(TAG, "CEC controller not available to send volume key event.");
1765 return;
1766 }
1767 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1768 if (localDevice == null) {
1769 Slog.w(TAG, "Local device " + deviceType
1770 + " not available to send volume key event.");
1771 return;
1772 }
1773 localDevice.sendVolumeKeyEvent(keyCode, isPressed);
1774 }
1775 });
1776 }
1777
1778 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001779 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001780 enforceAccessPermission();
Amy014b22f2019-02-04 12:55:40 -08001781 int pid = Binder.getCallingPid();
1782 Slog.d(TAG, "Proccess pid: " + pid + " is calling oneTouchPlay.");
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001783 runOnServiceThread(new Runnable() {
1784 @Override
1785 public void run() {
1786 HdmiControlService.this.oneTouchPlay(callback);
1787 }
1788 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001789 }
1790
1791 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001792 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001793 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001794 runOnServiceThread(new Runnable() {
1795 @Override
1796 public void run() {
1797 HdmiControlService.this.queryDisplayStatus(callback);
1798 }
1799 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001800 }
1801
1802 @Override
Madhava Srinivasan154e0192019-08-26 22:15:31 +02001803 public void addHdmiControlStatusChangeListener(
1804 final IHdmiControlStatusChangeListener listener) {
1805 enforceAccessPermission();
1806 HdmiControlService.this.addHdmiControlStatusChangeListener(listener);
1807 }
1808
1809 @Override
1810 public void removeHdmiControlStatusChangeListener(
1811 final IHdmiControlStatusChangeListener listener) {
1812 enforceAccessPermission();
1813 HdmiControlService.this.removeHdmiControlStatusChangeListener(listener);
1814 }
1815
1816 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001817 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001818 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001819 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001820 }
1821
1822 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001823 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001824 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001825 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001826 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001827
1828 @Override
1829 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1830 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001831 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001832 }
1833
1834 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001835 public List<HdmiPortInfo> getPortInfo() {
1836 enforceAccessPermission();
Amy6a58a342019-05-16 18:24:24 -07001837 return HdmiControlService.this.getPortInfo() == null
1838 ? Collections.<HdmiPortInfo>emptyList()
1839 : HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001840 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001841
1842 @Override
1843 public boolean canChangeSystemAudioMode() {
1844 enforceAccessPermission();
1845 HdmiCecLocalDeviceTv tv = tv();
1846 if (tv == null) {
1847 return false;
1848 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001849 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001850 }
1851
1852 @Override
1853 public boolean getSystemAudioMode() {
Shubang Lu00b976a2018-08-01 18:11:46 -07001854 // TODO(shubang): handle getSystemAudioMode() for all device types
Jungshik Jangea67c182014-06-19 22:19:20 +09001855 enforceAccessPermission();
1856 HdmiCecLocalDeviceTv tv = tv();
Shubang Lu00b976a2018-08-01 18:11:46 -07001857 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1858 return (tv != null && tv.isSystemAudioActivated())
1859 || (audioSystem != null && audioSystem.isSystemAudioActivated());
Jungshik Jangea67c182014-06-19 22:19:20 +09001860 }
1861
1862 @Override
Amyd58d0aa2018-10-18 14:08:57 -07001863 public int getPhysicalAddress() {
1864 enforceAccessPermission();
1865 synchronized (mLock) {
1866 return mPhysicalAddress;
1867 }
1868 }
1869
1870 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001871 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1872 enforceAccessPermission();
1873 runOnServiceThread(new Runnable() {
1874 @Override
1875 public void run() {
1876 HdmiCecLocalDeviceTv tv = tv();
1877 if (tv == null) {
1878 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001879 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001880 return;
1881 }
1882 tv.changeSystemAudioMode(enabled, callback);
1883 }
1884 });
1885 }
1886
1887 @Override
1888 public void addSystemAudioModeChangeListener(
1889 final IHdmiSystemAudioModeChangeListener listener) {
1890 enforceAccessPermission();
1891 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1892 }
1893
1894 @Override
1895 public void removeSystemAudioModeChangeListener(
1896 final IHdmiSystemAudioModeChangeListener listener) {
1897 enforceAccessPermission();
1898 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1899 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001900
1901 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001902 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1903 enforceAccessPermission();
1904 HdmiControlService.this.setInputChangeListener(listener);
1905 }
1906
1907 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001908 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001909 enforceAccessPermission();
1910 // No need to hold the lock for obtaining TV device as the local device instance
1911 // is preserved while the HDMI control is enabled.
1912 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001913 synchronized (mLock) {
1914 List<HdmiDeviceInfo> cecDevices = (tv == null)
1915 ? Collections.<HdmiDeviceInfo>emptyList()
1916 : tv.getSafeExternalInputsLocked();
1917 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001918 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001919 }
1920
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001921 // Returns all the CEC devices on the bus including system audio, switch,
1922 // even those of reserved type.
1923 @Override
1924 public List<HdmiDeviceInfo> getDeviceList() {
1925 enforceAccessPermission();
1926 HdmiCecLocalDeviceTv tv = tv();
Amy6f031af2018-10-30 16:38:33 -07001927 if (tv != null) {
1928 synchronized (mLock) {
1929 return tv.getSafeCecDevicesLocked();
1930 }
1931 } else {
1932 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1933 synchronized (mLock) {
1934 return (audioSystem == null)
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001935 ? Collections.<HdmiDeviceInfo>emptyList()
Amy6f031af2018-10-30 16:38:33 -07001936 : audioSystem.getSafeCecDevicesLocked();
1937 }
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001938 }
1939 }
1940
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001941 @Override
Amy6f031af2018-10-30 16:38:33 -07001942 public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
1943 enforceAccessPermission();
1944 runOnServiceThread(new Runnable() {
1945 @Override
1946 public void run() {
Amy4879d5c2018-11-13 16:06:15 -08001947 Slog.w(TAG, "Device "
1948 + logicalAddress + " power status is " + powerStatus
1949 + " before standby command sent out");
1950 sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1951 getRemoteControlSourceAddress(), logicalAddress));
Amy6f031af2018-10-30 16:38:33 -07001952 }
1953 });
1954 }
1955
1956 @Override
1957 public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
1958 // TODO(amyjojo): implement the method
1959 }
1960
1961 @Override
Amyd5a15142019-03-22 11:22:05 -07001962 // TODO(b/128427908): add a result callback
Amy6f031af2018-10-30 16:38:33 -07001963 public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
1964 enforceAccessPermission();
1965 runOnServiceThread(new Runnable() {
1966 @Override
1967 public void run() {
1968 HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
1969 getRemoteControlSourceAddress(), physicalAddress);
1970 if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
1971 if (getSwitchDevice() != null) {
1972 getSwitchDevice().handleSetStreamPath(setStreamPath);
1973 } else {
1974 Slog.e(TAG, "Can't get the correct local device to handle routing.");
1975 }
Amy6f031af2018-10-30 16:38:33 -07001976 }
Amycdd25e92018-11-13 12:22:16 -08001977 sendCecCommand(setStreamPath);
Amy6f031af2018-10-30 16:38:33 -07001978 }
1979 });
1980 }
1981
1982 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001983 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1984 final int maxIndex) {
1985 enforceAccessPermission();
1986 runOnServiceThread(new Runnable() {
1987 @Override
1988 public void run() {
1989 HdmiCecLocalDeviceTv tv = tv();
1990 if (tv == null) {
1991 Slog.w(TAG, "Local tv device not available");
1992 return;
1993 }
1994 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1995 }
1996 });
1997 }
1998
1999 @Override
2000 public void setSystemAudioMute(final boolean mute) {
2001 enforceAccessPermission();
2002 runOnServiceThread(new Runnable() {
2003 @Override
2004 public void run() {
2005 HdmiCecLocalDeviceTv tv = tv();
2006 if (tv == null) {
2007 Slog.w(TAG, "Local tv device not available");
2008 return;
2009 }
2010 tv.changeMute(mute);
2011 }
2012 });
2013 }
2014
2015 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09002016 public void setArcMode(final boolean enabled) {
2017 enforceAccessPermission();
2018 runOnServiceThread(new Runnable() {
2019 @Override
2020 public void run() {
2021 HdmiCecLocalDeviceTv tv = tv();
2022 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002023 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09002024 return;
2025 }
2026 }
2027 });
2028 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09002029
2030 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002031 public void setProhibitMode(final boolean enabled) {
2032 enforceAccessPermission();
2033 if (!isTvDevice()) {
2034 return;
2035 }
2036 HdmiControlService.this.setProhibitMode(enabled);
2037 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002038
2039 @Override
2040 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
2041 final int deviceType) {
2042 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09002043 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002044 }
2045
2046 @Override
2047 public void sendVendorCommand(final int deviceType, final int targetAddress,
2048 final byte[] params, final boolean hasVendorId) {
2049 enforceAccessPermission();
2050 runOnServiceThread(new Runnable() {
2051 @Override
2052 public void run() {
2053 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2054 if (device == null) {
2055 Slog.w(TAG, "Local device not available");
2056 return;
2057 }
2058 if (hasVendorId) {
2059 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
2060 device.getDeviceInfo().getLogicalAddress(), targetAddress,
2061 getVendorId(), params));
2062 } else {
2063 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
2064 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
2065 }
2066 }
2067 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002068 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09002069
2070 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002071 public void sendStandby(final int deviceType, final int deviceId) {
2072 enforceAccessPermission();
2073 runOnServiceThread(new Runnable() {
2074 @Override
2075 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09002076 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
2077 if (mhlDevice != null) {
2078 mhlDevice.sendStandby();
2079 return;
2080 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002081 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2082 if (device == null) {
Amy777abd72018-09-10 16:11:33 -07002083 device = audioSystem();
2084 }
2085 if (device == null) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002086 Slog.w(TAG, "Local device not available");
2087 return;
2088 }
2089 device.sendStandby(deviceId);
2090 }
2091 });
2092 }
2093
2094 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002095 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09002096 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002097 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09002098 }
2099
2100 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09002101 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09002102 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09002103 runOnServiceThread(new Runnable() {
2104 @Override
2105 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002106 if (!isTvDeviceEnabled()) {
2107 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09002108 return;
2109 }
2110 tv().startOneTouchRecord(recorderAddress, recordSource);
2111 }
2112 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09002113 }
2114
2115 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09002116 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09002117 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09002118 runOnServiceThread(new Runnable() {
2119 @Override
2120 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002121 if (!isTvDeviceEnabled()) {
2122 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09002123 return;
2124 }
2125 tv().stopOneTouchRecord(recorderAddress);
2126 }
2127 });
2128 }
2129
2130 @Override
2131 public void startTimerRecording(final int recorderAddress, final int sourceType,
2132 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09002133 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09002134 runOnServiceThread(new Runnable() {
2135 @Override
2136 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002137 if (!isTvDeviceEnabled()) {
2138 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09002139 return;
2140 }
2141 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
2142 }
2143 });
2144 }
2145
2146 @Override
2147 public void clearTimerRecording(final int recorderAddress, final int sourceType,
2148 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09002149 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09002150 runOnServiceThread(new Runnable() {
2151 @Override
2152 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002153 if (!isTvDeviceEnabled()) {
2154 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09002155 return;
2156 }
2157 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
2158 }
2159 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09002160 }
Jungshik Jangf4249322014-08-21 14:17:05 +09002161
2162 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002163 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09002164 final byte[] data) {
2165 enforceAccessPermission();
2166 runOnServiceThread(new Runnable() {
2167 @Override
2168 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09002169 if (!isControlEnabled()) {
2170 Slog.w(TAG, "Hdmi control is disabled.");
2171 return ;
2172 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002173 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09002174 if (device == null) {
2175 Slog.w(TAG, "Invalid port id:" + portId);
2176 return;
2177 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002178 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09002179 }
2180 });
2181 }
2182
2183 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002184 public void addHdmiMhlVendorCommandListener(
2185 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002186 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002187 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002188 }
Terry Heo959d2db2014-08-28 16:45:41 +09002189
2190 @Override
Donghyun Chob3515642017-03-02 13:47:40 +09002191 public void setStandbyMode(final boolean isStandbyModeOn) {
2192 enforceAccessPermission();
2193 runOnServiceThread(new Runnable() {
2194 @Override
2195 public void run() {
2196 HdmiControlService.this.setStandbyMode(isStandbyModeOn);
2197 }
2198 });
2199 }
2200
2201 @Override
Marvin Raminda665a62020-03-16 14:17:58 +01002202 public boolean isHdmiCecVolumeControlEnabled() {
2203 enforceAccessPermission();
2204 return HdmiControlService.this.isHdmiCecVolumeControlEnabled();
2205 }
2206
2207 @Override
2208 public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
2209 enforceAccessPermission();
Marvin Ramin26f53722020-05-07 17:17:32 +02002210 HdmiControlService.this.setHdmiCecVolumeControlEnabled(isHdmiCecVolumeControlEnabled);
Marvin Raminda665a62020-03-16 14:17:58 +01002211 }
2212
2213 @Override
Shubangc480a712018-06-11 18:02:42 -07002214 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
2215 final boolean isMute) {
2216 enforceAccessPermission();
2217 runOnServiceThread(new Runnable() {
2218 @Override
2219 public void run() {
2220 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2221 if (device == null) {
2222 Slog.w(TAG, "Local device not available");
2223 return;
2224 }
2225 if (audioSystem() == null) {
2226 Slog.w(TAG, "audio system is not available");
2227 return;
2228 }
Shubang17b29342018-07-19 17:58:48 -07002229 if (!audioSystem().isSystemAudioActivated()) {
Shubangc480a712018-06-11 18:02:42 -07002230 Slog.w(TAG, "audio system is not in system audio mode");
2231 return;
2232 }
Nick Chalko01b979c2018-10-19 14:54:30 -07002233 audioSystem().reportAudioStatus(Constants.ADDR_TV);
Shubangc480a712018-06-11 18:02:42 -07002234 }
2235 });
2236 }
2237
2238 @Override
Amy4ad4e782018-10-17 17:48:49 -07002239 public void setSystemAudioModeOnForAudioOnlySource() {
2240 enforceAccessPermission();
2241 runOnServiceThread(new Runnable() {
2242 @Override
2243 public void run() {
2244 if (!isAudioSystemDevice()) {
2245 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
2246 return;
2247 }
Amy7e3b6d92018-11-21 11:52:07 -08002248 if (audioSystem() == null) {
2249 Slog.e(TAG, "Audio System local device is not registered");
2250 return;
2251 }
Amy4ad4e782018-10-17 17:48:49 -07002252 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
2253 Slog.e(TAG, "System Audio Mode is not supported.");
2254 return;
2255 }
2256 sendCecCommand(
2257 HdmiCecMessageBuilder.buildSetSystemAudioMode(
2258 audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
2259 }
2260 });
2261 }
2262
2263 @Override
Terry Heo959d2db2014-08-28 16:45:41 +09002264 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06002265 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
Terry Heo959d2db2014-08-28 16:45:41 +09002266 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
2267
Terry Heo959d2db2014-08-28 16:45:41 +09002268 pw.println("mProhibitMode: " + mProhibitMode);
Nick Chalkob9e48e22018-10-23 06:59:39 -07002269 pw.println("mPowerStatus: " + mPowerStatus);
2270
2271 // System settings
2272 pw.println("System_settings:");
2273 pw.increaseIndent();
2274 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
2275 pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
Amy489454f2019-01-24 19:06:57 -08002276 pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
Marvin Raminda665a62020-03-16 14:17:58 +01002277 pw.println("mHdmiCecVolumeControlEnabled " + mHdmiCecVolumeControlEnabled);
Nick Chalkob9e48e22018-10-23 06:59:39 -07002278 pw.decreaseIndent();
Jinsuk Kim61c94d12015-01-15 07:00:28 +09002279
2280 pw.println("mMhlController: ");
2281 pw.increaseIndent();
2282 mMhlController.dump(pw);
2283 pw.decreaseIndent();
2284
Nick Chalkob9e48e22018-10-23 06:59:39 -07002285 HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
2286 if (mCecController != null) {
2287 pw.println("mCecController: ");
2288 pw.increaseIndent();
2289 mCecController.dump(pw);
2290 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +09002291 }
Terry Heo959d2db2014-08-28 16:45:41 +09002292 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002293 }
2294
Amy6f031af2018-10-30 16:38:33 -07002295 // Get the source address to send out commands to devices connected to the current device
2296 // when other services interact with HdmiControlService.
2297 private int getRemoteControlSourceAddress() {
2298 if (isAudioSystemDevice()) {
2299 return audioSystem().getDeviceInfo().getLogicalAddress();
2300 } else if (isPlaybackDevice()) {
2301 return playback().getDeviceInfo().getLogicalAddress();
2302 }
2303 return ADDR_UNREGISTERED;
2304 }
2305
2306 // Get the switch device to do CEC routing control
2307 @Nullable
2308 private HdmiCecLocalDeviceSource getSwitchDevice() {
2309 if (isAudioSystemDevice()) {
2310 return audioSystem();
2311 }
2312 if (isPlaybackDevice()) {
2313 return playback();
2314 }
2315 return null;
2316 }
2317
Jungshik Janga5b74142014-06-23 18:03:10 +09002318 @ServiceThreadOnly
Amyf5a866e2019-09-17 16:31:48 -07002319 @VisibleForTesting
2320 protected void oneTouchPlay(final IHdmiControlCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09002321 assertRunOnServiceThread();
Amyf5a866e2019-09-17 16:31:48 -07002322 if (!mAddressAllocated) {
2323 mOtpCallbackPendingAddressAllocation = callback;
2324 Slog.d(TAG, "Local device is under address allocation. "
2325 + "Save OTP callback for later process.");
2326 return;
2327 }
2328
Amy848a9f22018-08-27 17:21:26 -07002329 HdmiCecLocalDeviceSource source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002330 if (source == null) {
Amy848a9f22018-08-27 17:21:26 -07002331 source = audioSystem();
2332 }
2333
2334 if (source == null) {
2335 Slog.w(TAG, "Local source device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002336 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002337 return;
2338 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002339 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002340 }
2341
Jungshik Janga5b74142014-06-23 18:03:10 +09002342 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002343 private void queryDisplayStatus(final IHdmiControlCallback callback) {
2344 assertRunOnServiceThread();
Amy702b4ad2019-08-21 16:24:03 -07002345 if (!mAddressAllocated) {
2346 mDisplayStatusCallback = callback;
2347 Slog.d(TAG, "Local device is under address allocation. "
2348 + "Queue display callback for later process.");
2349 return;
2350 }
2351
Jungshik Jang79c58a42014-06-16 16:45:36 +09002352 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002353 if (source == null) {
2354 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002355 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002356 return;
2357 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002358 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002359 }
2360
Madhava Srinivasan154e0192019-08-26 22:15:31 +02002361 private void addHdmiControlStatusChangeListener(
2362 final IHdmiControlStatusChangeListener listener) {
2363 final HdmiControlStatusChangeListenerRecord record =
2364 new HdmiControlStatusChangeListenerRecord(listener);
2365 try {
2366 listener.asBinder().linkToDeath(record, 0);
2367 } catch (RemoteException e) {
2368 Slog.w(TAG, "Listener already died");
2369 return;
2370 }
2371 synchronized (mLock) {
2372 mHdmiControlStatusChangeListenerRecords.add(record);
2373 }
2374
2375 // Inform the listener of the initial state of each HDMI port by generating
2376 // hotplug events.
2377 runOnServiceThread(new Runnable() {
2378 @Override
2379 public void run() {
2380 synchronized (mLock) {
2381 if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
2382 }
2383
2384 // Return the current status of mHdmiControlEnabled;
2385 synchronized (mLock) {
2386 invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
2387 }
2388 }
2389 });
2390 }
2391
2392 private void removeHdmiControlStatusChangeListener(
2393 final IHdmiControlStatusChangeListener listener) {
2394 synchronized (mLock) {
2395 for (HdmiControlStatusChangeListenerRecord record :
2396 mHdmiControlStatusChangeListenerRecords) {
2397 if (record.mListener.asBinder() == listener.asBinder()) {
2398 listener.asBinder().unlinkToDeath(record, 0);
2399 mHdmiControlStatusChangeListenerRecords.remove(record);
2400 break;
2401 }
2402 }
2403 }
2404 }
2405
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002406 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
2407 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002408 try {
2409 listener.asBinder().linkToDeath(record, 0);
2410 } catch (RemoteException e) {
2411 Slog.w(TAG, "Listener already died");
2412 return;
2413 }
2414 synchronized (mLock) {
2415 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002416 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002417
2418 // Inform the listener of the initial state of each HDMI port by generating
2419 // hotplug events.
2420 runOnServiceThread(new Runnable() {
2421 @Override
2422 public void run() {
2423 synchronized (mLock) {
2424 if (!mHotplugEventListenerRecords.contains(record)) return;
2425 }
Amy6a58a342019-05-16 18:24:24 -07002426 for (HdmiPortInfo port : getPortInfo()) {
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002427 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
2428 mCecController.isConnected(port.getId()));
2429 synchronized (mLock) {
2430 invokeHotplugEventListenerLocked(listener, event);
2431 }
2432 }
2433 }
2434 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002435 }
2436
2437 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
2438 synchronized (mLock) {
2439 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2440 if (record.mListener.asBinder() == listener.asBinder()) {
2441 listener.asBinder().unlinkToDeath(record, 0);
2442 mHotplugEventListenerRecords.remove(record);
2443 break;
2444 }
2445 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002446 }
2447 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002448
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002449 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002450 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
2451 try {
2452 listener.asBinder().linkToDeath(record, 0);
2453 } catch (RemoteException e) {
2454 Slog.w(TAG, "Listener already died");
2455 return;
2456 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002457 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002458 mDeviceEventListenerRecords.add(record);
2459 }
2460 }
2461
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002462 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002463 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002464 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002465 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002466 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002467 } catch (RemoteException e) {
2468 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002469 }
2470 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002471 }
2472 }
2473
Jungshik Jangea67c182014-06-19 22:19:20 +09002474 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
2475 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
2476 listener);
2477 try {
2478 listener.asBinder().linkToDeath(record, 0);
2479 } catch (RemoteException e) {
2480 Slog.w(TAG, "Listener already died");
2481 return;
2482 }
2483 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09002484 mSystemAudioModeChangeListenerRecords.add(record);
2485 }
2486 }
2487
2488 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
2489 synchronized (mLock) {
2490 for (SystemAudioModeChangeListenerRecord record :
2491 mSystemAudioModeChangeListenerRecords) {
2492 if (record.mListener.asBinder() == listener) {
2493 listener.asBinder().unlinkToDeath(record, 0);
2494 mSystemAudioModeChangeListenerRecords.remove(record);
2495 break;
2496 }
2497 }
Jungshik Jangea67c182014-06-19 22:19:20 +09002498 }
2499 }
2500
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002501 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09002502 private final IHdmiInputChangeListener mListener;
2503
2504 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
2505 mListener = listener;
2506 }
2507
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002508 @Override
2509 public void binderDied() {
2510 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09002511 if (mInputChangeListenerRecord == this) {
2512 mInputChangeListenerRecord = null;
2513 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002514 }
2515 }
2516 }
2517
2518 private void setInputChangeListener(IHdmiInputChangeListener listener) {
2519 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002520 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002521 try {
2522 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
2523 } catch (RemoteException e) {
2524 Slog.w(TAG, "Listener already died");
2525 return;
2526 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002527 }
2528 }
2529
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002530 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002531 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002532 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002533 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002534 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002535 } catch (RemoteException e) {
2536 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
2537 }
2538 }
2539 }
2540 }
2541
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002542 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09002543 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002544 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002545 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002546 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002547 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002548 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002549 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002550 }
2551 }
2552
2553 byte[] invokeRecordRequestListener(int recorderAddress) {
2554 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002555 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002556 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002557 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002558 } catch (RemoteException e) {
2559 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002560 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002561 }
2562 return EmptyArray.BYTE;
2563 }
2564 }
2565
Jungshik Jang326aef02014-11-05 12:50:35 +09002566 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002567 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002568 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002569 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002570 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002571 } catch (RemoteException e) {
2572 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
2573 }
2574 }
2575 }
2576 }
2577
Jungshik Jang326aef02014-11-05 12:50:35 +09002578 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002579 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002580 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002581 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002582 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002583 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002584 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
2585 }
2586 }
2587 }
2588 }
2589
Jungshik Jang326aef02014-11-05 12:50:35 +09002590 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002591 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002592 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002593 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002594 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
2595 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09002596 } catch (RemoteException e) {
2597 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002598 }
2599 }
2600 }
2601 }
2602
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002603 private void invokeCallback(IHdmiControlCallback callback, int result) {
2604 try {
2605 callback.onComplete(result);
2606 } catch (RemoteException e) {
2607 Slog.e(TAG, "Invoking callback failed:" + e);
2608 }
2609 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09002610
Jungshik Jangf4249322014-08-21 14:17:05 +09002611 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09002612 boolean enabled) {
2613 try {
2614 listener.onStatusChanged(enabled);
2615 } catch (RemoteException e) {
2616 Slog.e(TAG, "Invoking callback failed:" + e);
2617 }
2618 }
2619
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002620 private void announceHotplugEvent(int portId, boolean connected) {
2621 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002622 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002623 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2624 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002625 }
2626 }
2627 }
2628
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002629 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09002630 HdmiHotplugEvent event) {
2631 try {
2632 listener.onReceived(event);
2633 } catch (RemoteException e) {
2634 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
2635 }
Jungshik Jange81e1082014-06-05 15:37:59 +09002636 }
2637
Madhava Srinivasan154e0192019-08-26 22:15:31 +02002638 private void announceHdmiControlStatusChange(boolean isEnabled) {
2639 assertRunOnServiceThread();
2640 synchronized (mLock) {
2641 for (HdmiControlStatusChangeListenerRecord record :
2642 mHdmiControlStatusChangeListenerRecords) {
2643 invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled);
2644 }
2645 }
2646 }
2647
2648 private void invokeHdmiControlStatusChangeListenerLocked(
2649 IHdmiControlStatusChangeListener listener, boolean isEnabled) {
2650 if (isEnabled) {
2651 queryDisplayStatus(new IHdmiControlCallback.Stub() {
2652 public void onComplete(int status) {
2653 boolean isAvailable = true;
2654 if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
2655 || status == HdmiControlManager.RESULT_EXCEPTION
2656 || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
2657 isAvailable = false;
2658 }
2659
2660 try {
2661 listener.onStatusChange(isEnabled, isAvailable);
2662 } catch (RemoteException e) {
2663 Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
2664 + " isAvailable: " + isAvailable, e);
2665 }
2666 }
2667 });
2668 return;
2669 }
2670
2671 try {
2672 listener.onStatusChange(isEnabled, false);
2673 } catch (RemoteException e) {
2674 Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
2675 + " isAvailable: " + false, e);
2676 }
2677 }
2678
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09002679 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002680 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09002681 }
2682
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002683 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09002684 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002685 }
2686
Amyb887fa02018-06-21 11:22:13 -07002687 boolean isAudioSystemDevice() {
2688 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2689 }
2690
Amy34037422018-09-06 13:21:08 -07002691 boolean isPlaybackDevice() {
2692 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
2693 }
2694
Amy066db152018-10-04 09:54:51 -07002695 boolean isSwitchDevice() {
2696 return SystemProperties.getBoolean(
Amy17ee20f2018-10-11 11:08:23 -07002697 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Amy066db152018-10-04 09:54:51 -07002698 }
2699
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002700 boolean isTvDeviceEnabled() {
2701 return isTvDevice() && tv() != null;
2702 }
2703
Amy34037422018-09-06 13:21:08 -07002704 protected HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002705 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002706 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002707 }
Jungshik Janga858d222014-06-23 17:17:47 +09002708
Shubangc480a712018-06-11 18:02:42 -07002709 public HdmiCecLocalDeviceAudioSystem audioSystem() {
2710 return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
2711 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2712 }
2713
Jungshik Janga858d222014-06-23 17:17:47 +09002714 AudioManager getAudioManager() {
2715 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
2716 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09002717
2718 boolean isControlEnabled() {
2719 synchronized (mLock) {
2720 return mHdmiControlEnabled;
2721 }
2722 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002723
Jungshik Jangf67113f2014-08-22 16:27:19 +09002724 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002725 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002726 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002727 return mPowerStatus;
2728 }
2729
Jungshik Jangf67113f2014-08-22 16:27:19 +09002730 @ServiceThreadOnly
Robert Horvath04848a32019-11-29 16:14:42 +01002731 @VisibleForTesting
2732 void setPowerStatus(int powerStatus) {
2733 assertRunOnServiceThread();
2734 mPowerStatus = powerStatus;
2735 }
2736
2737 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002738 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002739 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002740 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2741 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002742 }
2743
Jungshik Jangf67113f2014-08-22 16:27:19 +09002744 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002745 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002746 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002747 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2748 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002749 }
2750
Jungshik Jangf67113f2014-08-22 16:27:19 +09002751 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002752 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002753 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002754 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002755 }
2756
2757 @ServiceThreadOnly
2758 void wakeUp() {
2759 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002760 mWakeUpMessageReceived = true;
Michael Wrighte3001042019-02-05 00:13:14 +00002761 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
2762 "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002763 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2764 // the intent, the sequence will continue at onWakeUp().
2765 }
2766
2767 @ServiceThreadOnly
2768 void standby() {
2769 assertRunOnServiceThread();
Donghyun Cho02920a02016-10-11 17:17:34 +09002770 if (!canGoToStandby()) {
2771 return;
2772 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002773 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002774 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002775 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2776 // the intent, the sequence will continue at onStandby().
2777 }
2778
Donghyun Choafd26a22016-12-23 15:53:28 +09002779 boolean isWakeUpMessageReceived() {
2780 return mWakeUpMessageReceived;
2781 }
2782
Amybf8a4662018-07-02 12:34:24 -07002783 @VisibleForTesting
2784 boolean isStandbyMessageReceived() {
2785 return mStandbyMessageReceived;
2786 }
2787
Yuncheol Heo38db6292014-07-01 14:15:14 +09002788 @ServiceThreadOnly
2789 private void onWakeUp() {
2790 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002791 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002792 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002793 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002794 int startReason = INITIATED_BY_SCREEN_ON;
2795 if (mWakeUpMessageReceived) {
2796 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2797 }
2798 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002799 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002800 } else {
2801 Slog.i(TAG, "Device does not support HDMI-CEC.");
2802 }
2803 // TODO: Initialize MHL local devices.
2804 }
2805
2806 @ServiceThreadOnly
Amybf8a4662018-07-02 12:34:24 -07002807 @VisibleForTesting
2808 protected void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002809 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002810 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002811 invokeVendorCommandListenersOnControlStateChanged(false,
2812 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Amybf8a4662018-07-02 12:34:24 -07002813
2814 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2815
2816 if (!isStandbyMessageReceived() && !canGoToStandby()) {
Donghyun Cho02920a02016-10-11 17:17:34 +09002817 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Amybf8a4662018-07-02 12:34:24 -07002818 for (HdmiCecLocalDevice device : devices) {
2819 device.onStandby(mStandbyMessageReceived, standbyAction);
2820 }
Donghyun Cho02920a02016-10-11 17:17:34 +09002821 return;
2822 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002823
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002824 disableDevices(new PendingActionClearedCallback() {
2825 @Override
2826 public void onCleared(HdmiCecLocalDevice device) {
2827 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2828 devices.remove(device);
2829 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002830 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002831 // We will not clear local devices here, since some OEM/SOC will keep passing
2832 // the received packets until the application processor enters to the sleep
2833 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002834 }
2835 }
2836 });
2837 }
2838
Jinsuk Kime26d8332015-01-09 08:55:41 +09002839 private boolean canGoToStandby() {
2840 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2841 if (!device.canGoToStandby()) return false;
2842 }
2843 return true;
2844 }
2845
Terry Heo1ca0a432014-08-18 10:30:32 +09002846 @ServiceThreadOnly
2847 private void onLanguageChanged(String language) {
2848 assertRunOnServiceThread();
Nick Chalko9bed7172020-02-11 13:09:03 -08002849 mMenuLanguage = language;
Terry Heo1ca0a432014-08-18 10:30:32 +09002850
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002851 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002852 tv().broadcastMenuLanguage(language);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002853 mCecController.setLanguage(language);
Terry Heo1ca0a432014-08-18 10:30:32 +09002854 }
2855 }
2856
Nick Chalko9bed7172020-02-11 13:09:03 -08002857 /**
2858 * Gets the CEC menu language.
2859 *
2860 * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu
2861 * Language>}.
2862 * See HDMI 1.4b section CEC 13.6.2
2863 *
2864 * @see {@link Locale#getISO3Language()}
2865 */
Jungshik Jangf67113f2014-08-22 16:27:19 +09002866 @ServiceThreadOnly
2867 String getLanguage() {
2868 assertRunOnServiceThread();
Nick Chalko9bed7172020-02-11 13:09:03 -08002869 return mMenuLanguage;
Jungshik Jangf67113f2014-08-22 16:27:19 +09002870 }
2871
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002872 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002873 if (mCecController != null) {
2874 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2875 device.disableDevice(mStandbyMessageReceived, callback);
2876 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002877 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002878 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002879 }
2880
Yuncheol Heo38db6292014-07-01 14:15:14 +09002881 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002882 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002883 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002884 if (mCecController == null) {
2885 return;
2886 }
2887 mCecController.clearLogicalAddress();
2888 mCecController.clearLocalDevices();
2889 }
2890
2891 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002892 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002893 assertRunOnServiceThread();
2894 Slog.v(TAG, "onStandbyCompleted");
2895
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002896 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002897 return;
2898 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002899 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002900 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002901 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002902 }
2903 mStandbyMessageReceived = false;
Amyb887fa02018-06-21 11:22:13 -07002904 if (!isAudioSystemDevice()) {
2905 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2906 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2907 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002908 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002909
Jinsuk Kim119160a2014-07-07 18:48:10 +09002910 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2911 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2912 try {
2913 listener.asBinder().linkToDeath(record, 0);
2914 } catch (RemoteException e) {
2915 Slog.w(TAG, "Listener already died");
2916 return;
2917 }
2918 synchronized (mLock) {
2919 mVendorCommandListenerRecords.add(record);
2920 }
2921 }
2922
Yuncheol Heo0608b932014-10-13 16:39:18 +09002923 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2924 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002925 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002926 if (mVendorCommandListenerRecords.isEmpty()) {
2927 return false;
2928 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002929 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2930 if (record.mDeviceType != deviceType) {
2931 continue;
2932 }
2933 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002934 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002935 } catch (RemoteException e) {
2936 Slog.e(TAG, "Failed to notify vendor command reception", e);
2937 }
2938 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002939 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002940 }
2941 }
2942
Yuncheol Heo0608b932014-10-13 16:39:18 +09002943 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2944 synchronized (mLock) {
2945 if (mVendorCommandListenerRecords.isEmpty()) {
2946 return false;
2947 }
2948 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2949 try {
2950 record.mListener.onControlStateChanged(enabled, reason);
2951 } catch (RemoteException e) {
2952 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2953 }
2954 }
2955 return true;
2956 }
2957 }
2958
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002959 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2960 HdmiMhlVendorCommandListenerRecord record =
2961 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002962 try {
2963 listener.asBinder().linkToDeath(record, 0);
2964 } catch (RemoteException e) {
2965 Slog.w(TAG, "Listener already died.");
2966 return;
2967 }
2968
2969 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002970 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002971 }
2972 }
2973
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002974 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002975 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002976 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002977 try {
2978 record.mListener.onReceived(portId, offest, length, data);
2979 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002980 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002981 }
2982 }
2983 }
2984 }
2985
Donghyun Chob3515642017-03-02 13:47:40 +09002986 void setStandbyMode(boolean isStandbyModeOn) {
2987 assertRunOnServiceThread();
2988 if (isPowerOnOrTransient() && isStandbyModeOn) {
2989 mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2990 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2991 if (playback() != null) {
2992 playback().sendStandby(0 /* unused */);
2993 }
2994 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
Michael Wrighte3001042019-02-05 00:13:14 +00002995 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
2996 "android.server.hdmi:WAKE");
Donghyun Chob3515642017-03-02 13:47:40 +09002997 if (playback() != null) {
2998 oneTouchPlay(new IHdmiControlCallback.Stub() {
2999 @Override
3000 public void onComplete(int result) {
3001 if (result != HdmiControlManager.RESULT_SUCCESS) {
3002 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
3003 }
3004 }
3005 });
3006 }
3007 }
3008 }
3009
Marvin Raminda665a62020-03-16 14:17:58 +01003010 void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
Marvin Raminda665a62020-03-16 14:17:58 +01003011 synchronized (mLock) {
3012 mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
3013
3014 boolean storedValue = readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
3015 true);
3016 if (storedValue != isHdmiCecVolumeControlEnabled) {
3017 HdmiLogger.debug("Changing HDMI CEC volume control feature state: %s",
3018 isHdmiCecVolumeControlEnabled);
3019 writeBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
3020 isHdmiCecVolumeControlEnabled);
3021 }
3022 }
3023 }
3024
3025 boolean isHdmiCecVolumeControlEnabled() {
Marvin Raminda665a62020-03-16 14:17:58 +01003026 synchronized (mLock) {
3027 return mHdmiCecVolumeControlEnabled;
3028 }
3029 }
3030
Jinsuk Kim4d43d932014-07-03 16:43:58 +09003031 boolean isProhibitMode() {
3032 synchronized (mLock) {
3033 return mProhibitMode;
3034 }
3035 }
3036
3037 void setProhibitMode(boolean enabled) {
3038 synchronized (mLock) {
3039 mProhibitMode = enabled;
3040 }
3041 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003042
Amy489454f2019-01-24 19:06:57 -08003043 boolean isSystemAudioActivated() {
3044 synchronized (mLock) {
3045 return mSystemAudioActivated;
3046 }
3047 }
3048
3049 void setSystemAudioActivated(boolean on) {
3050 synchronized (mLock) {
3051 mSystemAudioActivated = on;
3052 }
3053 }
3054
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003055 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +09003056 void setCecOption(int key, boolean value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09003057 assertRunOnServiceThread();
3058 mCecController.setOption(key, value);
3059 }
3060
3061 @ServiceThreadOnly
3062 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003063 assertRunOnServiceThread();
3064
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003065 synchronized (mLock) {
3066 mHdmiControlEnabled = enabled;
3067 }
3068
3069 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09003070 enableHdmiControlService();
Marvin Raminda665a62020-03-16 14:17:58 +01003071 setHdmiCecVolumeControlEnabled(
3072 readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true));
Yuncheol Heof1702482014-11-27 19:52:01 +09003073 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003074 }
Marvin Raminda665a62020-03-16 14:17:58 +01003075
3076 setHdmiCecVolumeControlEnabled(false);
Yuncheol Heof1702482014-11-27 19:52:01 +09003077 // Call the vendor handler before the service is disabled.
3078 invokeVendorCommandListenersOnControlStateChanged(false,
3079 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
3080 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
3081 // a chance to run.
3082 runOnServiceThread(new Runnable() {
3083 @Override
3084 public void run() {
3085 disableHdmiControlService();
3086 }
3087 });
Madhava Srinivasan154e0192019-08-26 22:15:31 +02003088 announceHdmiControlStatusChange(enabled);
3089
Yuncheol Heof1702482014-11-27 19:52:01 +09003090 return;
3091 }
3092
3093 @ServiceThreadOnly
3094 private void enableHdmiControlService() {
Amy718e41e2018-08-17 17:23:37 -07003095 mCecController.setOption(OptionKey.ENABLE_CEC, true);
Donghyun Chobc6e3722016-11-04 05:25:52 +09003096 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Yuncheol Heof1702482014-11-27 19:52:01 +09003097 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
3098
3099 initializeCec(INITIATED_BY_ENABLE_CEC);
3100 }
3101
3102 @ServiceThreadOnly
3103 private void disableHdmiControlService() {
3104 disableDevices(new PendingActionClearedCallback() {
3105 @Override
3106 public void onCleared(HdmiCecLocalDevice device) {
3107 assertRunOnServiceThread();
3108 mCecController.flush(new Runnable() {
3109 @Override
3110 public void run() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09003111 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Amy718e41e2018-08-17 17:23:37 -07003112 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
Yuncheol Heof1702482014-11-27 19:52:01 +09003113 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
3114 clearLocalDevices();
3115 }
3116 });
3117 }
3118 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003119 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09003120
3121 @ServiceThreadOnly
3122 void setActivePortId(int portId) {
3123 assertRunOnServiceThread();
3124 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003125
3126 // Resets last input for MHL, which stays valid only after the MHL device was selected,
3127 // and no further switching is done.
3128 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09003129 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09003130
Amy8a9eae72019-04-29 13:27:05 -07003131 ActiveSource getLocalActiveSource() {
Amy123ec402018-09-25 10:56:31 -07003132 synchronized (mLock) {
3133 return mActiveSource;
3134 }
3135 }
3136
3137 void setActiveSource(int logicalAddress, int physicalAddress) {
3138 synchronized (mLock) {
3139 mActiveSource.logicalAddress = logicalAddress;
3140 mActiveSource.physicalAddress = physicalAddress;
3141 }
Amy02cf1ab2019-04-30 13:25:41 -07003142 // If the current device is a source device, check if the current Active Source matches
3143 // the local device info. Set mIsActiveSource of the local device accordingly.
3144 for (HdmiCecLocalDevice device : getAllLocalDevices()) {
3145 // mIsActiveSource only exists in source device, ignore this setting if the current
3146 // device is not an HdmiCecLocalDeviceSource.
3147 if (!(device instanceof HdmiCecLocalDeviceSource)) {
3148 continue;
3149 }
3150 if (logicalAddress == device.getDeviceInfo().getLogicalAddress()
3151 && physicalAddress == getPhysicalAddress()) {
3152 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(true);
3153 } else {
3154 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(false);
3155 }
3156 }
Amy123ec402018-09-25 10:56:31 -07003157 }
3158
3159 // This method should only be called when the device can be the active source
3160 // and all the device types call into this method.
3161 // For example, when receiving broadcast messages, all the device types will call this
3162 // method but only one of them will be the Active Source.
3163 protected void setAndBroadcastActiveSource(
Amyb9d7f432018-11-30 15:08:30 -08003164 int physicalAddress, int deviceType, int source) {
Amy123ec402018-09-25 10:56:31 -07003165 // If the device has both playback and audio system logical addresses,
3166 // playback will claim active source. Otherwise audio system will.
3167 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
3168 HdmiCecLocalDevicePlayback playback = playback();
3169 playback.setIsActiveSource(true);
3170 playback.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08003171 playback.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07003172 setActiveSource(playback.mAddress, physicalAddress);
3173 }
3174
3175 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
3176 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
3177 if (playback() != null) {
3178 audioSystem.setIsActiveSource(false);
3179 } else {
3180 audioSystem.setIsActiveSource(true);
3181 audioSystem.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08003182 audioSystem.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07003183 setActiveSource(audioSystem.mAddress, physicalAddress);
3184 }
3185 }
3186 }
3187
3188 // This method should only be called when the device can be the active source
3189 // and only one of the device types calls into this method.
3190 // For example, when receiving One Touch Play, only playback device handles it
3191 // and this method updates Active Source in all the device types sharing the same
3192 // Physical Address.
3193 protected void setAndBroadcastActiveSourceFromOneDeviceType(
3194 int sourceAddress, int physicalAddress) {
3195 // If the device has both playback and audio system logical addresses,
3196 // playback will claim active source. Otherwise audio system will.
3197 HdmiCecLocalDevicePlayback playback = playback();
3198 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
3199 if (playback != null) {
3200 playback.setIsActiveSource(true);
3201 playback.wakeUpIfActiveSource();
3202 playback.maySendActiveSource(sourceAddress);
3203 if (audioSystem != null) {
3204 audioSystem.setIsActiveSource(false);
3205 }
3206 setActiveSource(playback.mAddress, physicalAddress);
3207 } else {
3208 if (audioSystem != null) {
3209 audioSystem.setIsActiveSource(true);
3210 audioSystem.wakeUpIfActiveSource();
3211 audioSystem.maySendActiveSource(sourceAddress);
3212 setActiveSource(audioSystem.mAddress, physicalAddress);
3213 }
3214 }
3215 }
3216
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003217 @ServiceThreadOnly
3218 void setLastInputForMhl(int portId) {
3219 assertRunOnServiceThread();
3220 mLastInputMhl = portId;
3221 }
3222
3223 @ServiceThreadOnly
3224 int getLastInputForMhl() {
3225 assertRunOnServiceThread();
3226 return mLastInputMhl;
3227 }
3228
3229 /**
3230 * Performs input change, routing control for MHL device.
3231 *
3232 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
3233 * @param contentOn {@code true} if RAP data is content on; otherwise false
3234 */
3235 @ServiceThreadOnly
3236 void changeInputForMhl(int portId, boolean contentOn) {
3237 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09003238 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003239 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09003240 if (portId != Constants.INVALID_PORT_ID) {
3241 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
3242 @Override
3243 public void onComplete(int result) throws RemoteException {
3244 // Keep the last input to switch back later when RAP[ContentOff] is received.
3245 // This effectively sets the port to invalid one if the switching is for
3246 // RAP[ContentOff].
3247 setLastInputForMhl(lastInput);
3248 }
3249 });
3250 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003251 // MHL device is always directly connected to the port. Update the active port ID to avoid
3252 // unnecessary post-routing control task.
3253 tv().setActivePortId(portId);
3254
3255 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09003256 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003257 // may not be the MHL-enabled one. In this case the device info to be passed to
3258 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09003259 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09003260 HdmiDeviceInfo info = (device != null) ? device.getInfo()
3261 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003262 invokeInputChangeListener(info);
3263 }
3264
3265 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09003266 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09003267
3268 synchronized (mLock) {
3269 mMhlInputChangeEnabled = enabled;
3270 }
3271 }
3272
3273 boolean isMhlInputChangeEnabled() {
3274 synchronized (mLock) {
3275 return mMhlInputChangeEnabled;
3276 }
3277 }
Jungshik Jang339227d2014-08-25 15:37:20 +09003278
3279 @ServiceThreadOnly
3280 void displayOsd(int messageId) {
3281 assertRunOnServiceThread();
3282 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
3283 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
3284 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
3285 HdmiControlService.PERMISSION);
3286 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09003287
3288 @ServiceThreadOnly
3289 void displayOsd(int messageId, int extra) {
3290 assertRunOnServiceThread();
3291 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
3292 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09003293 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09003294 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
3295 HdmiControlService.PERMISSION);
3296 }
Jungshik Jang0792d372014-04-23 17:57:26 +09003297}