blob: 53f9ebcbd8ddf6ee3b791f7712556be1dfea9579 [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();
2210 runOnServiceThread(new Runnable() {
2211 @Override
2212 public void run() {
2213 HdmiControlService.this.setHdmiCecVolumeControlEnabled(
2214 isHdmiCecVolumeControlEnabled);
2215 }
2216 });
2217 }
2218
2219 @Override
Shubangc480a712018-06-11 18:02:42 -07002220 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
2221 final boolean isMute) {
2222 enforceAccessPermission();
2223 runOnServiceThread(new Runnable() {
2224 @Override
2225 public void run() {
2226 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2227 if (device == null) {
2228 Slog.w(TAG, "Local device not available");
2229 return;
2230 }
2231 if (audioSystem() == null) {
2232 Slog.w(TAG, "audio system is not available");
2233 return;
2234 }
Shubang17b29342018-07-19 17:58:48 -07002235 if (!audioSystem().isSystemAudioActivated()) {
Shubangc480a712018-06-11 18:02:42 -07002236 Slog.w(TAG, "audio system is not in system audio mode");
2237 return;
2238 }
Nick Chalko01b979c2018-10-19 14:54:30 -07002239 audioSystem().reportAudioStatus(Constants.ADDR_TV);
Shubangc480a712018-06-11 18:02:42 -07002240 }
2241 });
2242 }
2243
2244 @Override
Amy4ad4e782018-10-17 17:48:49 -07002245 public void setSystemAudioModeOnForAudioOnlySource() {
2246 enforceAccessPermission();
2247 runOnServiceThread(new Runnable() {
2248 @Override
2249 public void run() {
2250 if (!isAudioSystemDevice()) {
2251 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
2252 return;
2253 }
Amy7e3b6d92018-11-21 11:52:07 -08002254 if (audioSystem() == null) {
2255 Slog.e(TAG, "Audio System local device is not registered");
2256 return;
2257 }
Amy4ad4e782018-10-17 17:48:49 -07002258 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
2259 Slog.e(TAG, "System Audio Mode is not supported.");
2260 return;
2261 }
2262 sendCecCommand(
2263 HdmiCecMessageBuilder.buildSetSystemAudioMode(
2264 audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
2265 }
2266 });
2267 }
2268
2269 @Override
Terry Heo959d2db2014-08-28 16:45:41 +09002270 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06002271 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
Terry Heo959d2db2014-08-28 16:45:41 +09002272 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
2273
Terry Heo959d2db2014-08-28 16:45:41 +09002274 pw.println("mProhibitMode: " + mProhibitMode);
Nick Chalkob9e48e22018-10-23 06:59:39 -07002275 pw.println("mPowerStatus: " + mPowerStatus);
2276
2277 // System settings
2278 pw.println("System_settings:");
2279 pw.increaseIndent();
2280 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
2281 pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
Amy489454f2019-01-24 19:06:57 -08002282 pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
Marvin Raminda665a62020-03-16 14:17:58 +01002283 pw.println("mHdmiCecVolumeControlEnabled " + mHdmiCecVolumeControlEnabled);
Nick Chalkob9e48e22018-10-23 06:59:39 -07002284 pw.decreaseIndent();
Jinsuk Kim61c94d12015-01-15 07:00:28 +09002285
2286 pw.println("mMhlController: ");
2287 pw.increaseIndent();
2288 mMhlController.dump(pw);
2289 pw.decreaseIndent();
2290
Nick Chalkob9e48e22018-10-23 06:59:39 -07002291 HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
2292 if (mCecController != null) {
2293 pw.println("mCecController: ");
2294 pw.increaseIndent();
2295 mCecController.dump(pw);
2296 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +09002297 }
Terry Heo959d2db2014-08-28 16:45:41 +09002298 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002299 }
2300
Amy6f031af2018-10-30 16:38:33 -07002301 // Get the source address to send out commands to devices connected to the current device
2302 // when other services interact with HdmiControlService.
2303 private int getRemoteControlSourceAddress() {
2304 if (isAudioSystemDevice()) {
2305 return audioSystem().getDeviceInfo().getLogicalAddress();
2306 } else if (isPlaybackDevice()) {
2307 return playback().getDeviceInfo().getLogicalAddress();
2308 }
2309 return ADDR_UNREGISTERED;
2310 }
2311
2312 // Get the switch device to do CEC routing control
2313 @Nullable
2314 private HdmiCecLocalDeviceSource getSwitchDevice() {
2315 if (isAudioSystemDevice()) {
2316 return audioSystem();
2317 }
2318 if (isPlaybackDevice()) {
2319 return playback();
2320 }
2321 return null;
2322 }
2323
Jungshik Janga5b74142014-06-23 18:03:10 +09002324 @ServiceThreadOnly
Amyf5a866e2019-09-17 16:31:48 -07002325 @VisibleForTesting
2326 protected void oneTouchPlay(final IHdmiControlCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09002327 assertRunOnServiceThread();
Amyf5a866e2019-09-17 16:31:48 -07002328 if (!mAddressAllocated) {
2329 mOtpCallbackPendingAddressAllocation = callback;
2330 Slog.d(TAG, "Local device is under address allocation. "
2331 + "Save OTP callback for later process.");
2332 return;
2333 }
2334
Amy848a9f22018-08-27 17:21:26 -07002335 HdmiCecLocalDeviceSource source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002336 if (source == null) {
Amy848a9f22018-08-27 17:21:26 -07002337 source = audioSystem();
2338 }
2339
2340 if (source == null) {
2341 Slog.w(TAG, "Local source device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002342 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002343 return;
2344 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002345 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002346 }
2347
Jungshik Janga5b74142014-06-23 18:03:10 +09002348 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002349 private void queryDisplayStatus(final IHdmiControlCallback callback) {
2350 assertRunOnServiceThread();
Amy702b4ad2019-08-21 16:24:03 -07002351 if (!mAddressAllocated) {
2352 mDisplayStatusCallback = callback;
2353 Slog.d(TAG, "Local device is under address allocation. "
2354 + "Queue display callback for later process.");
2355 return;
2356 }
2357
Jungshik Jang79c58a42014-06-16 16:45:36 +09002358 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002359 if (source == null) {
2360 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002361 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002362 return;
2363 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002364 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002365 }
2366
Madhava Srinivasan154e0192019-08-26 22:15:31 +02002367 private void addHdmiControlStatusChangeListener(
2368 final IHdmiControlStatusChangeListener listener) {
2369 final HdmiControlStatusChangeListenerRecord record =
2370 new HdmiControlStatusChangeListenerRecord(listener);
2371 try {
2372 listener.asBinder().linkToDeath(record, 0);
2373 } catch (RemoteException e) {
2374 Slog.w(TAG, "Listener already died");
2375 return;
2376 }
2377 synchronized (mLock) {
2378 mHdmiControlStatusChangeListenerRecords.add(record);
2379 }
2380
2381 // Inform the listener of the initial state of each HDMI port by generating
2382 // hotplug events.
2383 runOnServiceThread(new Runnable() {
2384 @Override
2385 public void run() {
2386 synchronized (mLock) {
2387 if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
2388 }
2389
2390 // Return the current status of mHdmiControlEnabled;
2391 synchronized (mLock) {
2392 invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
2393 }
2394 }
2395 });
2396 }
2397
2398 private void removeHdmiControlStatusChangeListener(
2399 final IHdmiControlStatusChangeListener listener) {
2400 synchronized (mLock) {
2401 for (HdmiControlStatusChangeListenerRecord record :
2402 mHdmiControlStatusChangeListenerRecords) {
2403 if (record.mListener.asBinder() == listener.asBinder()) {
2404 listener.asBinder().unlinkToDeath(record, 0);
2405 mHdmiControlStatusChangeListenerRecords.remove(record);
2406 break;
2407 }
2408 }
2409 }
2410 }
2411
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002412 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
2413 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002414 try {
2415 listener.asBinder().linkToDeath(record, 0);
2416 } catch (RemoteException e) {
2417 Slog.w(TAG, "Listener already died");
2418 return;
2419 }
2420 synchronized (mLock) {
2421 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002422 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002423
2424 // Inform the listener of the initial state of each HDMI port by generating
2425 // hotplug events.
2426 runOnServiceThread(new Runnable() {
2427 @Override
2428 public void run() {
2429 synchronized (mLock) {
2430 if (!mHotplugEventListenerRecords.contains(record)) return;
2431 }
Amy6a58a342019-05-16 18:24:24 -07002432 for (HdmiPortInfo port : getPortInfo()) {
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002433 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
2434 mCecController.isConnected(port.getId()));
2435 synchronized (mLock) {
2436 invokeHotplugEventListenerLocked(listener, event);
2437 }
2438 }
2439 }
2440 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002441 }
2442
2443 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
2444 synchronized (mLock) {
2445 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2446 if (record.mListener.asBinder() == listener.asBinder()) {
2447 listener.asBinder().unlinkToDeath(record, 0);
2448 mHotplugEventListenerRecords.remove(record);
2449 break;
2450 }
2451 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002452 }
2453 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002454
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002455 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002456 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
2457 try {
2458 listener.asBinder().linkToDeath(record, 0);
2459 } catch (RemoteException e) {
2460 Slog.w(TAG, "Listener already died");
2461 return;
2462 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002463 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002464 mDeviceEventListenerRecords.add(record);
2465 }
2466 }
2467
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002468 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002469 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002470 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002471 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002472 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002473 } catch (RemoteException e) {
2474 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002475 }
2476 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002477 }
2478 }
2479
Jungshik Jangea67c182014-06-19 22:19:20 +09002480 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
2481 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
2482 listener);
2483 try {
2484 listener.asBinder().linkToDeath(record, 0);
2485 } catch (RemoteException e) {
2486 Slog.w(TAG, "Listener already died");
2487 return;
2488 }
2489 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09002490 mSystemAudioModeChangeListenerRecords.add(record);
2491 }
2492 }
2493
2494 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
2495 synchronized (mLock) {
2496 for (SystemAudioModeChangeListenerRecord record :
2497 mSystemAudioModeChangeListenerRecords) {
2498 if (record.mListener.asBinder() == listener) {
2499 listener.asBinder().unlinkToDeath(record, 0);
2500 mSystemAudioModeChangeListenerRecords.remove(record);
2501 break;
2502 }
2503 }
Jungshik Jangea67c182014-06-19 22:19:20 +09002504 }
2505 }
2506
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002507 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09002508 private final IHdmiInputChangeListener mListener;
2509
2510 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
2511 mListener = listener;
2512 }
2513
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002514 @Override
2515 public void binderDied() {
2516 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09002517 if (mInputChangeListenerRecord == this) {
2518 mInputChangeListenerRecord = null;
2519 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002520 }
2521 }
2522 }
2523
2524 private void setInputChangeListener(IHdmiInputChangeListener listener) {
2525 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002526 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002527 try {
2528 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
2529 } catch (RemoteException e) {
2530 Slog.w(TAG, "Listener already died");
2531 return;
2532 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002533 }
2534 }
2535
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002536 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002537 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002538 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002539 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002540 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002541 } catch (RemoteException e) {
2542 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
2543 }
2544 }
2545 }
2546 }
2547
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002548 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09002549 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002550 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002551 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002552 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002553 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002554 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002555 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002556 }
2557 }
2558
2559 byte[] invokeRecordRequestListener(int recorderAddress) {
2560 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002561 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002562 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002563 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002564 } catch (RemoteException e) {
2565 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002566 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002567 }
2568 return EmptyArray.BYTE;
2569 }
2570 }
2571
Jungshik Jang326aef02014-11-05 12:50:35 +09002572 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002573 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002574 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002575 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002576 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002577 } catch (RemoteException e) {
2578 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
2579 }
2580 }
2581 }
2582 }
2583
Jungshik Jang326aef02014-11-05 12:50:35 +09002584 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002585 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002586 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002587 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002588 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002589 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002590 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
2591 }
2592 }
2593 }
2594 }
2595
Jungshik Jang326aef02014-11-05 12:50:35 +09002596 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002597 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002598 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002599 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002600 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
2601 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09002602 } catch (RemoteException e) {
2603 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002604 }
2605 }
2606 }
2607 }
2608
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002609 private void invokeCallback(IHdmiControlCallback callback, int result) {
2610 try {
2611 callback.onComplete(result);
2612 } catch (RemoteException e) {
2613 Slog.e(TAG, "Invoking callback failed:" + e);
2614 }
2615 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09002616
Jungshik Jangf4249322014-08-21 14:17:05 +09002617 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09002618 boolean enabled) {
2619 try {
2620 listener.onStatusChanged(enabled);
2621 } catch (RemoteException e) {
2622 Slog.e(TAG, "Invoking callback failed:" + e);
2623 }
2624 }
2625
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002626 private void announceHotplugEvent(int portId, boolean connected) {
2627 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002628 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002629 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2630 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002631 }
2632 }
2633 }
2634
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002635 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09002636 HdmiHotplugEvent event) {
2637 try {
2638 listener.onReceived(event);
2639 } catch (RemoteException e) {
2640 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
2641 }
Jungshik Jange81e1082014-06-05 15:37:59 +09002642 }
2643
Madhava Srinivasan154e0192019-08-26 22:15:31 +02002644 private void announceHdmiControlStatusChange(boolean isEnabled) {
2645 assertRunOnServiceThread();
2646 synchronized (mLock) {
2647 for (HdmiControlStatusChangeListenerRecord record :
2648 mHdmiControlStatusChangeListenerRecords) {
2649 invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled);
2650 }
2651 }
2652 }
2653
2654 private void invokeHdmiControlStatusChangeListenerLocked(
2655 IHdmiControlStatusChangeListener listener, boolean isEnabled) {
2656 if (isEnabled) {
2657 queryDisplayStatus(new IHdmiControlCallback.Stub() {
2658 public void onComplete(int status) {
2659 boolean isAvailable = true;
2660 if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
2661 || status == HdmiControlManager.RESULT_EXCEPTION
2662 || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
2663 isAvailable = false;
2664 }
2665
2666 try {
2667 listener.onStatusChange(isEnabled, isAvailable);
2668 } catch (RemoteException e) {
2669 Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
2670 + " isAvailable: " + isAvailable, e);
2671 }
2672 }
2673 });
2674 return;
2675 }
2676
2677 try {
2678 listener.onStatusChange(isEnabled, false);
2679 } catch (RemoteException e) {
2680 Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
2681 + " isAvailable: " + false, e);
2682 }
2683 }
2684
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09002685 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002686 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09002687 }
2688
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002689 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09002690 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002691 }
2692
Amyb887fa02018-06-21 11:22:13 -07002693 boolean isAudioSystemDevice() {
2694 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2695 }
2696
Amy34037422018-09-06 13:21:08 -07002697 boolean isPlaybackDevice() {
2698 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
2699 }
2700
Amy066db152018-10-04 09:54:51 -07002701 boolean isSwitchDevice() {
2702 return SystemProperties.getBoolean(
Amy17ee20f2018-10-11 11:08:23 -07002703 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Amy066db152018-10-04 09:54:51 -07002704 }
2705
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002706 boolean isTvDeviceEnabled() {
2707 return isTvDevice() && tv() != null;
2708 }
2709
Amy34037422018-09-06 13:21:08 -07002710 protected HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002711 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002712 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002713 }
Jungshik Janga858d222014-06-23 17:17:47 +09002714
Shubangc480a712018-06-11 18:02:42 -07002715 public HdmiCecLocalDeviceAudioSystem audioSystem() {
2716 return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
2717 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2718 }
2719
Jungshik Janga858d222014-06-23 17:17:47 +09002720 AudioManager getAudioManager() {
2721 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
2722 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09002723
2724 boolean isControlEnabled() {
2725 synchronized (mLock) {
2726 return mHdmiControlEnabled;
2727 }
2728 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002729
Jungshik Jangf67113f2014-08-22 16:27:19 +09002730 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002731 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002732 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002733 return mPowerStatus;
2734 }
2735
Jungshik Jangf67113f2014-08-22 16:27:19 +09002736 @ServiceThreadOnly
Robert Horvath04848a32019-11-29 16:14:42 +01002737 @VisibleForTesting
2738 void setPowerStatus(int powerStatus) {
2739 assertRunOnServiceThread();
2740 mPowerStatus = powerStatus;
2741 }
2742
2743 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002744 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002745 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002746 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2747 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002748 }
2749
Jungshik Jangf67113f2014-08-22 16:27:19 +09002750 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002751 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002752 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002753 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2754 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002755 }
2756
Jungshik Jangf67113f2014-08-22 16:27:19 +09002757 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002758 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002759 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002760 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002761 }
2762
2763 @ServiceThreadOnly
2764 void wakeUp() {
2765 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002766 mWakeUpMessageReceived = true;
Michael Wrighte3001042019-02-05 00:13:14 +00002767 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
2768 "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002769 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2770 // the intent, the sequence will continue at onWakeUp().
2771 }
2772
2773 @ServiceThreadOnly
2774 void standby() {
2775 assertRunOnServiceThread();
Donghyun Cho02920a02016-10-11 17:17:34 +09002776 if (!canGoToStandby()) {
2777 return;
2778 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002779 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002780 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002781 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2782 // the intent, the sequence will continue at onStandby().
2783 }
2784
Donghyun Choafd26a22016-12-23 15:53:28 +09002785 boolean isWakeUpMessageReceived() {
2786 return mWakeUpMessageReceived;
2787 }
2788
Amybf8a4662018-07-02 12:34:24 -07002789 @VisibleForTesting
2790 boolean isStandbyMessageReceived() {
2791 return mStandbyMessageReceived;
2792 }
2793
Yuncheol Heo38db6292014-07-01 14:15:14 +09002794 @ServiceThreadOnly
2795 private void onWakeUp() {
2796 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002797 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002798 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002799 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002800 int startReason = INITIATED_BY_SCREEN_ON;
2801 if (mWakeUpMessageReceived) {
2802 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2803 }
2804 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002805 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002806 } else {
2807 Slog.i(TAG, "Device does not support HDMI-CEC.");
2808 }
2809 // TODO: Initialize MHL local devices.
2810 }
2811
2812 @ServiceThreadOnly
Amybf8a4662018-07-02 12:34:24 -07002813 @VisibleForTesting
2814 protected void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002815 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002816 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002817 invokeVendorCommandListenersOnControlStateChanged(false,
2818 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Amybf8a4662018-07-02 12:34:24 -07002819
2820 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2821
2822 if (!isStandbyMessageReceived() && !canGoToStandby()) {
Donghyun Cho02920a02016-10-11 17:17:34 +09002823 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Amybf8a4662018-07-02 12:34:24 -07002824 for (HdmiCecLocalDevice device : devices) {
2825 device.onStandby(mStandbyMessageReceived, standbyAction);
2826 }
Donghyun Cho02920a02016-10-11 17:17:34 +09002827 return;
2828 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002829
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002830 disableDevices(new PendingActionClearedCallback() {
2831 @Override
2832 public void onCleared(HdmiCecLocalDevice device) {
2833 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2834 devices.remove(device);
2835 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002836 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002837 // We will not clear local devices here, since some OEM/SOC will keep passing
2838 // the received packets until the application processor enters to the sleep
2839 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002840 }
2841 }
2842 });
2843 }
2844
Jinsuk Kime26d8332015-01-09 08:55:41 +09002845 private boolean canGoToStandby() {
2846 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2847 if (!device.canGoToStandby()) return false;
2848 }
2849 return true;
2850 }
2851
Terry Heo1ca0a432014-08-18 10:30:32 +09002852 @ServiceThreadOnly
2853 private void onLanguageChanged(String language) {
2854 assertRunOnServiceThread();
Nick Chalko9bed7172020-02-11 13:09:03 -08002855 mMenuLanguage = language;
Terry Heo1ca0a432014-08-18 10:30:32 +09002856
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002857 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002858 tv().broadcastMenuLanguage(language);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002859 mCecController.setLanguage(language);
Terry Heo1ca0a432014-08-18 10:30:32 +09002860 }
2861 }
2862
Nick Chalko9bed7172020-02-11 13:09:03 -08002863 /**
2864 * Gets the CEC menu language.
2865 *
2866 * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu
2867 * Language>}.
2868 * See HDMI 1.4b section CEC 13.6.2
2869 *
2870 * @see {@link Locale#getISO3Language()}
2871 */
Jungshik Jangf67113f2014-08-22 16:27:19 +09002872 @ServiceThreadOnly
2873 String getLanguage() {
2874 assertRunOnServiceThread();
Nick Chalko9bed7172020-02-11 13:09:03 -08002875 return mMenuLanguage;
Jungshik Jangf67113f2014-08-22 16:27:19 +09002876 }
2877
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002878 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002879 if (mCecController != null) {
2880 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2881 device.disableDevice(mStandbyMessageReceived, callback);
2882 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002883 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002884 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002885 }
2886
Yuncheol Heo38db6292014-07-01 14:15:14 +09002887 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002888 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002889 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002890 if (mCecController == null) {
2891 return;
2892 }
2893 mCecController.clearLogicalAddress();
2894 mCecController.clearLocalDevices();
2895 }
2896
2897 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002898 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002899 assertRunOnServiceThread();
2900 Slog.v(TAG, "onStandbyCompleted");
2901
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002902 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002903 return;
2904 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002905 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002906 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002907 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002908 }
2909 mStandbyMessageReceived = false;
Amyb887fa02018-06-21 11:22:13 -07002910 if (!isAudioSystemDevice()) {
2911 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2912 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2913 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002914 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002915
Jinsuk Kim119160a2014-07-07 18:48:10 +09002916 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2917 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2918 try {
2919 listener.asBinder().linkToDeath(record, 0);
2920 } catch (RemoteException e) {
2921 Slog.w(TAG, "Listener already died");
2922 return;
2923 }
2924 synchronized (mLock) {
2925 mVendorCommandListenerRecords.add(record);
2926 }
2927 }
2928
Yuncheol Heo0608b932014-10-13 16:39:18 +09002929 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2930 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002931 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002932 if (mVendorCommandListenerRecords.isEmpty()) {
2933 return false;
2934 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002935 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2936 if (record.mDeviceType != deviceType) {
2937 continue;
2938 }
2939 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002940 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002941 } catch (RemoteException e) {
2942 Slog.e(TAG, "Failed to notify vendor command reception", e);
2943 }
2944 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002945 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002946 }
2947 }
2948
Yuncheol Heo0608b932014-10-13 16:39:18 +09002949 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2950 synchronized (mLock) {
2951 if (mVendorCommandListenerRecords.isEmpty()) {
2952 return false;
2953 }
2954 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2955 try {
2956 record.mListener.onControlStateChanged(enabled, reason);
2957 } catch (RemoteException e) {
2958 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2959 }
2960 }
2961 return true;
2962 }
2963 }
2964
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002965 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2966 HdmiMhlVendorCommandListenerRecord record =
2967 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002968 try {
2969 listener.asBinder().linkToDeath(record, 0);
2970 } catch (RemoteException e) {
2971 Slog.w(TAG, "Listener already died.");
2972 return;
2973 }
2974
2975 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002976 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002977 }
2978 }
2979
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002980 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002981 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002982 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002983 try {
2984 record.mListener.onReceived(portId, offest, length, data);
2985 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002986 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002987 }
2988 }
2989 }
2990 }
2991
Donghyun Chob3515642017-03-02 13:47:40 +09002992 void setStandbyMode(boolean isStandbyModeOn) {
2993 assertRunOnServiceThread();
2994 if (isPowerOnOrTransient() && isStandbyModeOn) {
2995 mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2996 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2997 if (playback() != null) {
2998 playback().sendStandby(0 /* unused */);
2999 }
3000 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
Michael Wrighte3001042019-02-05 00:13:14 +00003001 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
3002 "android.server.hdmi:WAKE");
Donghyun Chob3515642017-03-02 13:47:40 +09003003 if (playback() != null) {
3004 oneTouchPlay(new IHdmiControlCallback.Stub() {
3005 @Override
3006 public void onComplete(int result) {
3007 if (result != HdmiControlManager.RESULT_SUCCESS) {
3008 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
3009 }
3010 }
3011 });
3012 }
3013 }
3014 }
3015
Marvin Raminda665a62020-03-16 14:17:58 +01003016 void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
3017 assertRunOnServiceThread();
3018 synchronized (mLock) {
3019 mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
3020
3021 boolean storedValue = readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
3022 true);
3023 if (storedValue != isHdmiCecVolumeControlEnabled) {
3024 HdmiLogger.debug("Changing HDMI CEC volume control feature state: %s",
3025 isHdmiCecVolumeControlEnabled);
3026 writeBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
3027 isHdmiCecVolumeControlEnabled);
3028 }
3029 }
3030 }
3031
3032 boolean isHdmiCecVolumeControlEnabled() {
3033 assertRunOnServiceThread();
3034 synchronized (mLock) {
3035 return mHdmiCecVolumeControlEnabled;
3036 }
3037 }
3038
Jinsuk Kim4d43d932014-07-03 16:43:58 +09003039 boolean isProhibitMode() {
3040 synchronized (mLock) {
3041 return mProhibitMode;
3042 }
3043 }
3044
3045 void setProhibitMode(boolean enabled) {
3046 synchronized (mLock) {
3047 mProhibitMode = enabled;
3048 }
3049 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003050
Amy489454f2019-01-24 19:06:57 -08003051 boolean isSystemAudioActivated() {
3052 synchronized (mLock) {
3053 return mSystemAudioActivated;
3054 }
3055 }
3056
3057 void setSystemAudioActivated(boolean on) {
3058 synchronized (mLock) {
3059 mSystemAudioActivated = on;
3060 }
3061 }
3062
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003063 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +09003064 void setCecOption(int key, boolean value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09003065 assertRunOnServiceThread();
3066 mCecController.setOption(key, value);
3067 }
3068
3069 @ServiceThreadOnly
3070 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003071 assertRunOnServiceThread();
3072
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003073 synchronized (mLock) {
3074 mHdmiControlEnabled = enabled;
3075 }
3076
3077 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09003078 enableHdmiControlService();
Marvin Raminda665a62020-03-16 14:17:58 +01003079 setHdmiCecVolumeControlEnabled(
3080 readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true));
Yuncheol Heof1702482014-11-27 19:52:01 +09003081 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003082 }
Marvin Raminda665a62020-03-16 14:17:58 +01003083
3084 setHdmiCecVolumeControlEnabled(false);
Yuncheol Heof1702482014-11-27 19:52:01 +09003085 // Call the vendor handler before the service is disabled.
3086 invokeVendorCommandListenersOnControlStateChanged(false,
3087 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
3088 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
3089 // a chance to run.
3090 runOnServiceThread(new Runnable() {
3091 @Override
3092 public void run() {
3093 disableHdmiControlService();
3094 }
3095 });
Madhava Srinivasan154e0192019-08-26 22:15:31 +02003096 announceHdmiControlStatusChange(enabled);
3097
Yuncheol Heof1702482014-11-27 19:52:01 +09003098 return;
3099 }
3100
3101 @ServiceThreadOnly
3102 private void enableHdmiControlService() {
Amy718e41e2018-08-17 17:23:37 -07003103 mCecController.setOption(OptionKey.ENABLE_CEC, true);
Donghyun Chobc6e3722016-11-04 05:25:52 +09003104 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Yuncheol Heof1702482014-11-27 19:52:01 +09003105 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
3106
3107 initializeCec(INITIATED_BY_ENABLE_CEC);
3108 }
3109
3110 @ServiceThreadOnly
3111 private void disableHdmiControlService() {
3112 disableDevices(new PendingActionClearedCallback() {
3113 @Override
3114 public void onCleared(HdmiCecLocalDevice device) {
3115 assertRunOnServiceThread();
3116 mCecController.flush(new Runnable() {
3117 @Override
3118 public void run() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09003119 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Amy718e41e2018-08-17 17:23:37 -07003120 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
Yuncheol Heof1702482014-11-27 19:52:01 +09003121 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
3122 clearLocalDevices();
3123 }
3124 });
3125 }
3126 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09003127 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09003128
3129 @ServiceThreadOnly
3130 void setActivePortId(int portId) {
3131 assertRunOnServiceThread();
3132 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003133
3134 // Resets last input for MHL, which stays valid only after the MHL device was selected,
3135 // and no further switching is done.
3136 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09003137 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09003138
Amy8a9eae72019-04-29 13:27:05 -07003139 ActiveSource getLocalActiveSource() {
Amy123ec402018-09-25 10:56:31 -07003140 synchronized (mLock) {
3141 return mActiveSource;
3142 }
3143 }
3144
3145 void setActiveSource(int logicalAddress, int physicalAddress) {
3146 synchronized (mLock) {
3147 mActiveSource.logicalAddress = logicalAddress;
3148 mActiveSource.physicalAddress = physicalAddress;
3149 }
Amy02cf1ab2019-04-30 13:25:41 -07003150 // If the current device is a source device, check if the current Active Source matches
3151 // the local device info. Set mIsActiveSource of the local device accordingly.
3152 for (HdmiCecLocalDevice device : getAllLocalDevices()) {
3153 // mIsActiveSource only exists in source device, ignore this setting if the current
3154 // device is not an HdmiCecLocalDeviceSource.
3155 if (!(device instanceof HdmiCecLocalDeviceSource)) {
3156 continue;
3157 }
3158 if (logicalAddress == device.getDeviceInfo().getLogicalAddress()
3159 && physicalAddress == getPhysicalAddress()) {
3160 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(true);
3161 } else {
3162 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(false);
3163 }
3164 }
Amy123ec402018-09-25 10:56:31 -07003165 }
3166
3167 // This method should only be called when the device can be the active source
3168 // and all the device types call into this method.
3169 // For example, when receiving broadcast messages, all the device types will call this
3170 // method but only one of them will be the Active Source.
3171 protected void setAndBroadcastActiveSource(
Amyb9d7f432018-11-30 15:08:30 -08003172 int physicalAddress, int deviceType, int source) {
Amy123ec402018-09-25 10:56:31 -07003173 // If the device has both playback and audio system logical addresses,
3174 // playback will claim active source. Otherwise audio system will.
3175 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
3176 HdmiCecLocalDevicePlayback playback = playback();
3177 playback.setIsActiveSource(true);
3178 playback.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08003179 playback.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07003180 setActiveSource(playback.mAddress, physicalAddress);
3181 }
3182
3183 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
3184 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
3185 if (playback() != null) {
3186 audioSystem.setIsActiveSource(false);
3187 } else {
3188 audioSystem.setIsActiveSource(true);
3189 audioSystem.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08003190 audioSystem.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07003191 setActiveSource(audioSystem.mAddress, physicalAddress);
3192 }
3193 }
3194 }
3195
3196 // This method should only be called when the device can be the active source
3197 // and only one of the device types calls into this method.
3198 // For example, when receiving One Touch Play, only playback device handles it
3199 // and this method updates Active Source in all the device types sharing the same
3200 // Physical Address.
3201 protected void setAndBroadcastActiveSourceFromOneDeviceType(
3202 int sourceAddress, int physicalAddress) {
3203 // If the device has both playback and audio system logical addresses,
3204 // playback will claim active source. Otherwise audio system will.
3205 HdmiCecLocalDevicePlayback playback = playback();
3206 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
3207 if (playback != null) {
3208 playback.setIsActiveSource(true);
3209 playback.wakeUpIfActiveSource();
3210 playback.maySendActiveSource(sourceAddress);
3211 if (audioSystem != null) {
3212 audioSystem.setIsActiveSource(false);
3213 }
3214 setActiveSource(playback.mAddress, physicalAddress);
3215 } else {
3216 if (audioSystem != null) {
3217 audioSystem.setIsActiveSource(true);
3218 audioSystem.wakeUpIfActiveSource();
3219 audioSystem.maySendActiveSource(sourceAddress);
3220 setActiveSource(audioSystem.mAddress, physicalAddress);
3221 }
3222 }
3223 }
3224
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003225 @ServiceThreadOnly
3226 void setLastInputForMhl(int portId) {
3227 assertRunOnServiceThread();
3228 mLastInputMhl = portId;
3229 }
3230
3231 @ServiceThreadOnly
3232 int getLastInputForMhl() {
3233 assertRunOnServiceThread();
3234 return mLastInputMhl;
3235 }
3236
3237 /**
3238 * Performs input change, routing control for MHL device.
3239 *
3240 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
3241 * @param contentOn {@code true} if RAP data is content on; otherwise false
3242 */
3243 @ServiceThreadOnly
3244 void changeInputForMhl(int portId, boolean contentOn) {
3245 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09003246 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003247 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09003248 if (portId != Constants.INVALID_PORT_ID) {
3249 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
3250 @Override
3251 public void onComplete(int result) throws RemoteException {
3252 // Keep the last input to switch back later when RAP[ContentOff] is received.
3253 // This effectively sets the port to invalid one if the switching is for
3254 // RAP[ContentOff].
3255 setLastInputForMhl(lastInput);
3256 }
3257 });
3258 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003259 // MHL device is always directly connected to the port. Update the active port ID to avoid
3260 // unnecessary post-routing control task.
3261 tv().setActivePortId(portId);
3262
3263 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09003264 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003265 // may not be the MHL-enabled one. In this case the device info to be passed to
3266 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09003267 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09003268 HdmiDeviceInfo info = (device != null) ? device.getInfo()
3269 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09003270 invokeInputChangeListener(info);
3271 }
3272
3273 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09003274 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09003275
3276 synchronized (mLock) {
3277 mMhlInputChangeEnabled = enabled;
3278 }
3279 }
3280
3281 boolean isMhlInputChangeEnabled() {
3282 synchronized (mLock) {
3283 return mMhlInputChangeEnabled;
3284 }
3285 }
Jungshik Jang339227d2014-08-25 15:37:20 +09003286
3287 @ServiceThreadOnly
3288 void displayOsd(int messageId) {
3289 assertRunOnServiceThread();
3290 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
3291 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
3292 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
3293 HdmiControlService.PERMISSION);
3294 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09003295
3296 @ServiceThreadOnly
3297 void displayOsd(int messageId, int extra) {
3298 assertRunOnServiceThread();
3299 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
3300 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09003301 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09003302 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
3303 HdmiControlService.PERMISSION);
3304 }
Jungshik Jang0792d372014-04-23 17:57:26 +09003305}