blob: d390d860aeeaec0988aaeed4436628c2d0fe7319 [file] [log] [blame]
Jungshik Jang0792d372014-04-23 17:57:26 +09001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.hdmi;
18
Jinsuk Kimed086452014-08-18 15:01:53 +090019import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
20import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
Shubang Lu00b976a2018-08-01 18:11:46 -070021
Amy17ee20f2018-10-11 11:08:23 -070022import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
Amy6f031af2018-10-30 16:38:33 -070023import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
Jinsuk Kim50084862014-08-07 13:11:40 +090024import static com.android.server.hdmi.Constants.DISABLED;
25import static com.android.server.hdmi.Constants.ENABLED;
Jinsuk Kim50084862014-08-07 13:11:40 +090026import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
27import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
28import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
Jinsuk Kim5b8cb002015-01-19 07:30:12 +090029import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
Amy3288f8a2018-10-23 18:55:34 -070030import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
Jinsuk Kim50084862014-08-07 13:11:40 +090031
Jungshik Jang0792d372014-04-23 17:57:26 +090032import android.annotation.Nullable;
Yuncheol Heo38db6292014-07-01 14:15:14 +090033import android.content.BroadcastReceiver;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090034import android.content.ContentResolver;
Jungshik Jang0792d372014-04-23 17:57:26 +090035import android.content.Context;
Yuncheol Heo38db6292014-07-01 14:15:14 +090036import android.content.Intent;
37import android.content.IntentFilter;
Jinsuk Kim50084862014-08-07 13:11:40 +090038import android.database.ContentObserver;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090039import android.hardware.hdmi.HdmiControlManager;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090040import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang60cffce2014-06-12 18:03:04 +090041import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090042import android.hardware.hdmi.HdmiPortInfo;
Jungshik Jangd643f762014-05-22 19:28:09 +090043import android.hardware.hdmi.IHdmiControlCallback;
44import android.hardware.hdmi.IHdmiControlService;
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +090045import android.hardware.hdmi.IHdmiDeviceEventListener;
Jungshik Jangd643f762014-05-22 19:28:09 +090046import android.hardware.hdmi.IHdmiHotplugEventListener;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090047import android.hardware.hdmi.IHdmiInputChangeListener;
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +090048import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090049import android.hardware.hdmi.IHdmiRecordListener;
Jungshik Jangea67c182014-06-19 22:19:20 +090050import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Jinsuk Kim119160a2014-07-07 18:48:10 +090051import android.hardware.hdmi.IHdmiVendorCommandListener;
Donghyun Chobc6e3722016-11-04 05:25:52 +090052import android.hardware.tv.cec.V1_0.OptionKey;
53import android.hardware.tv.cec.V1_0.SendMessageResult;
Jungshik Janga858d222014-06-23 17:17:47 +090054import android.media.AudioManager;
Jinsuk Kim7fa3a662014-11-07 15:20:24 +090055import android.media.tv.TvInputManager;
56import android.media.tv.TvInputManager.TvInputCallback;
Jinsuk Kim50084862014-08-07 13:11:40 +090057import android.net.Uri;
Jungshik Jang42c98002014-06-12 13:17:44 +090058import android.os.Build;
Jungshik Jang67ea5212014-05-15 14:05:24 +090059import android.os.Handler;
Jungshik Jang0792d372014-04-23 17:57:26 +090060import android.os.HandlerThread;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090061import android.os.IBinder;
Jungshik Jange9c77c82014-04-24 20:30:09 +090062import android.os.Looper;
Yuncheol Heo38db6292014-07-01 14:15:14 +090063import android.os.PowerManager;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090064import android.os.RemoteException;
Yuncheol Heo38db6292014-07-01 14:15:14 +090065import android.os.SystemClock;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090066import android.os.SystemProperties;
Jinsuk Kim50084862014-08-07 13:11:40 +090067import android.os.UserHandle;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090068import android.provider.Settings.Global;
Yuncheol Heo7d9acc72014-08-12 15:30:49 +090069import android.text.TextUtils;
Jinsuk Kim2b152012014-07-25 08:22:26 +090070import android.util.ArraySet;
Jungshik Jang0792d372014-04-23 17:57:26 +090071import android.util.Slog;
Jungshik Jang3ee65722014-06-03 16:22:30 +090072import android.util.SparseArray;
Jungshik Jang8b308d92014-05-29 21:52:28 +090073import android.util.SparseIntArray;
Shubang Lu00b976a2018-08-01 18:11:46 -070074
Jinsuk Kim4893c7e2014-06-19 14:13:22 +090075import com.android.internal.annotations.GuardedBy;
Amy1d0b1372018-05-24 14:36:25 -070076import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060077import com.android.internal.util.DumpUtils;
Terry Heo959d2db2014-08-28 16:45:41 +090078import com.android.internal.util.IndentingPrintWriter;
Jungshik Jang0792d372014-04-23 17:57:26 +090079import com.android.server.SystemService;
Jungshik Janga5b74142014-06-23 18:03:10 +090080import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang3ee65722014-06-03 16:22:30 +090081import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
Jinsuk Kim7e742062014-07-30 13:19:13 +090082import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090083import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
Shubang Lu00b976a2018-08-01 18:11:46 -070084
85import libcore.util.EmptyArray;
86
Terry Heo959d2db2014-08-28 16:45:41 +090087import java.io.FileDescriptor;
88import java.io.PrintWriter;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090089import java.util.ArrayList;
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +090090import java.util.Arrays;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090091import java.util.Collections;
Kyeongkab.Namec0aac92018-10-10 13:47:29 +090092import java.util.HashMap;
Jungshik Jang02bb4262014-05-23 16:48:31 +090093import java.util.List;
Terry Heo1ca0a432014-08-18 10:30:32 +090094import java.util.Locale;
Kyeongkab.Namec0aac92018-10-10 13:47:29 +090095import java.util.Map;
Jungshik Janga1fa91f2014-05-08 20:56:41 +090096
Jungshik Jang0792d372014-04-23 17:57:26 +090097/**
98 * Provides a service for sending and processing HDMI control messages,
99 * HDMI-CEC and MHL control command, and providing the information on both standard.
100 */
Amyd4f98992018-05-11 17:59:06 -0700101public class HdmiControlService extends SystemService {
Jungshik Jang0792d372014-04-23 17:57:26 +0900102 private static final String TAG = "HdmiControlService";
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900103 private final Locale HONG_KONG = new Locale("zh", "HK");
104 private final Locale MACAU = new Locale("zh", "MO");
Jungshik Jang0792d372014-04-23 17:57:26 +0900105
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900106 private static final Map<String, String> mTerminologyToBibliographicMap;
107 static {
108 mTerminologyToBibliographicMap = new HashMap<>();
109 // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
110 mTerminologyToBibliographicMap.put("sqi", "alb"); // Albanian
111 mTerminologyToBibliographicMap.put("hye", "arm"); // Armenian
112 mTerminologyToBibliographicMap.put("eus", "baq"); // Basque
113 mTerminologyToBibliographicMap.put("mya", "bur"); // Burmese
114 mTerminologyToBibliographicMap.put("ces", "cze"); // Czech
115 mTerminologyToBibliographicMap.put("nld", "dut"); // Dutch
116 mTerminologyToBibliographicMap.put("kat", "geo"); // Georgian
117 mTerminologyToBibliographicMap.put("deu", "ger"); // German
118 mTerminologyToBibliographicMap.put("ell", "gre"); // Greek
119 mTerminologyToBibliographicMap.put("fra", "fre"); // French
120 mTerminologyToBibliographicMap.put("isl", "ice"); // Icelandic
121 mTerminologyToBibliographicMap.put("mkd", "mac"); // Macedonian
122 mTerminologyToBibliographicMap.put("mri", "mao"); // Maori
123 mTerminologyToBibliographicMap.put("msa", "may"); // Malay
124 mTerminologyToBibliographicMap.put("fas", "per"); // Persian
125 mTerminologyToBibliographicMap.put("ron", "rum"); // Romanian
126 mTerminologyToBibliographicMap.put("slk", "slo"); // Slovak
127 mTerminologyToBibliographicMap.put("bod", "tib"); // Tibetan
128 mTerminologyToBibliographicMap.put("cym", "wel"); // Welsh
129 }
130
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +0900131 static final String PERMISSION = "android.permission.HDMI_CEC";
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900132
Shubanga3f59502018-06-05 16:32:53 -0700133 // The reason code to initiate initializeCec().
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900134 static final int INITIATED_BY_ENABLE_CEC = 0;
135 static final int INITIATED_BY_BOOT_UP = 1;
136 static final int INITIATED_BY_SCREEN_ON = 2;
137 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
Yuncheol Heob5021862014-09-02 10:36:04 +0900138 static final int INITIATED_BY_HOTPLUG = 4;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900139
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900140 // The reason code representing the intent action that drives the standby
141 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
142 // Intent.ACTION_SHUTDOWN.
143 static final int STANDBY_SCREEN_OFF = 0;
144 static final int STANDBY_SHUTDOWN = 1;
145
Amy123ec402018-09-25 10:56:31 -0700146 // Logical address of the active source.
147 @GuardedBy("mLock")
148 protected final ActiveSource mActiveSource = new ActiveSource();
149
Amy02f31152018-08-28 15:05:42 -0700150 private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
151 SystemProperties.getBoolean(
152 Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
153
Jungshik Jangd643f762014-05-22 19:28:09 +0900154 /**
155 * Interface to report send result.
156 */
157 interface SendMessageCallback {
158 /**
159 * Called when {@link HdmiControlService#sendCecCommand} is completed.
160 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900161 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900162 * <ul>
Donghyun Chobc6e3722016-11-04 05:25:52 +0900163 * <li>{@link SendMessageResult#SUCCESS}
164 * <li>{@link SendMessageResult#NACK}
165 * <li>{@link SendMessageResult#BUSY}
166 * <li>{@link SendMessageResult#FAIL}
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900167 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900168 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900169 void onSendCompleted(int error);
170 }
171
Jungshik Jang02bb4262014-05-23 16:48:31 +0900172 /**
173 * Interface to get a list of available logical devices.
174 */
175 interface DevicePollingCallback {
176 /**
177 * Called when device polling is finished.
178 *
179 * @param ackedAddress a list of logical addresses of available devices
180 */
181 void onPollingFinished(List<Integer> ackedAddress);
182 }
183
Terry Heo1ca0a432014-08-18 10:30:32 +0900184 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900185 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900186 @Override
187 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900188 assertRunOnServiceThread();
Amy3288f8a2018-10-23 18:55:34 -0700189 boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
Yuncheol Heo38db6292014-07-01 14:15:14 +0900190 switch (intent.getAction()) {
191 case Intent.ACTION_SCREEN_OFF:
Amy3288f8a2018-10-23 18:55:34 -0700192 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900193 onStandby(STANDBY_SCREEN_OFF);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900194 }
195 break;
196 case Intent.ACTION_SCREEN_ON:
197 if (isPowerStandbyOrTransient()) {
198 onWakeUp();
199 }
200 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900201 case Intent.ACTION_CONFIGURATION_CHANGED:
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900202 String language = getMenuLanguage();
Terry Heo1ca0a432014-08-18 10:30:32 +0900203 if (!mLanguage.equals(language)) {
204 onLanguageChanged(language);
205 }
206 break;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900207 case Intent.ACTION_SHUTDOWN:
Amy3288f8a2018-10-23 18:55:34 -0700208 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900209 onStandby(STANDBY_SHUTDOWN);
210 }
211 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900212 }
213 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900214
215 private String getMenuLanguage() {
216 Locale locale = Locale.getDefault();
217 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
218 // Android always returns "zho" for all Chinese variants.
219 // Use "bibliographic" code defined in CEC639-2 for traditional
220 // Chinese used in Taiwan/Hong Kong/Macau.
221 return "chi";
222 } else {
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900223 String language = locale.getISO3Language();
224
225 // locale.getISO3Language() returns terminology code and need to
226 // send it as bibliographic code instead since the Bibliographic
227 // codes of ISO/FDIS 639-2 shall be used.
228 // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
229 // But, as it depends on the locale, is not handled here.
230 if (mTerminologyToBibliographicMap.containsKey(language)) {
231 language = mTerminologyToBibliographicMap.get(language);
232 }
233
234 return language;
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900235 }
236 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900237 }
238
Jungshik Jang0792d372014-04-23 17:57:26 +0900239 // A thread to handle synchronous IO of CEC and MHL control service.
240 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
241 // and sparse call it shares a thread to handle IO operations.
242 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
243
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900244 // Used to synchronize the access to the service.
245 private final Object mLock = new Object();
246
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900247 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
248 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900249
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900250 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900251 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900252 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
253 new ArrayList<>();
254
Jungshik Jangf4249322014-08-21 14:17:05 +0900255 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900256 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900257 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
258 new ArrayList<>();
259
Jungshik Jangf4249322014-08-21 14:17:05 +0900260 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900261 @GuardedBy("mLock")
262 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
263 new ArrayList<>();
264
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900265 @GuardedBy("mLock")
266 private InputChangeListenerRecord mInputChangeListenerRecord;
267
Jungshik Jangb6591b82014-07-23 16:10:23 +0900268 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900269 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900270
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900271 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
272 // handling will be disabled and no request will be handled.
273 @GuardedBy("mLock")
274 private boolean mHdmiControlEnabled;
275
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900276 // Set to true while the service is in normal mode. While set to false, no input change is
277 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
278 // system upgrade, etc., a.k.a. "prohibit mode".
279 @GuardedBy("mLock")
280 private boolean mProhibitMode;
281
Jungshik Jangea67c182014-06-19 22:19:20 +0900282 // List of records for system audio mode change to handle the the caller killed in action.
283 private final ArrayList<SystemAudioModeChangeListenerRecord>
284 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
285
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900286 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900287 private final Handler mHandler = new Handler();
288
Jinsuk Kim50084862014-08-07 13:11:40 +0900289 private final SettingsObserver mSettingsObserver;
290
Jungshik Jangf4249322014-08-21 14:17:05 +0900291 private final HdmiControlBroadcastReceiver
292 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
293
Jungshik Jang0792d372014-04-23 17:57:26 +0900294 @Nullable
295 private HdmiCecController mCecController;
296
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900297 // HDMI port information. Stored in the unmodifiable list to keep the static information
298 // from being modified.
299 private List<HdmiPortInfo> mPortInfo;
300
Jinsuk Kim2b152012014-07-25 08:22:26 +0900301 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900302 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900303
304 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900305 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900306
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900307 // Map from port ID to HdmiDeviceInfo.
308 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
309
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900310 private HdmiCecMessageValidator mMessageValidator;
311
Yuncheol Heo38db6292014-07-01 14:15:14 +0900312 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900313 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900314
315 @ServiceThreadOnly
Terry Heo1ca0a432014-08-18 10:30:32 +0900316 private String mLanguage = Locale.getDefault().getISO3Language();
317
318 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900319 private boolean mStandbyMessageReceived = false;
320
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900321 @ServiceThreadOnly
322 private boolean mWakeUpMessageReceived = false;
323
Jungshik Jang867b4e02014-08-12 13:41:30 +0900324 @ServiceThreadOnly
325 private int mActivePortId = Constants.INVALID_PORT_ID;
326
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900327 // Set to true while the input change by MHL is allowed.
328 @GuardedBy("mLock")
329 private boolean mMhlInputChangeEnabled;
330
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900331 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900332 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900333 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
334 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900335
336 @GuardedBy("mLock")
337 private List<HdmiDeviceInfo> mMhlDevices;
338
339 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900340 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900341
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900342 @Nullable
343 private TvInputManager mTvInputManager;
344
Jinsuk Kime26d8332015-01-09 08:55:41 +0900345 @Nullable
346 private PowerManager mPowerManager;
347
Amy1d0b1372018-05-24 14:36:25 -0700348 @Nullable
349 private Looper mIoLooper;
350
Amyd58d0aa2018-10-18 14:08:57 -0700351 // Thread safe physical address
352 @GuardedBy("mLock")
353 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
354
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900355 // Last input port before switching to the MHL port. Should switch back to this port
356 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900357 // Gets invalidated if we go to other port/input.
358 @ServiceThreadOnly
359 private int mLastInputMhl = Constants.INVALID_PORT_ID;
360
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900361 // Set to true if the logical address allocation is completed.
362 private boolean mAddressAllocated = false;
363
364 // Buffer for processing the incoming cec messages while allocating logical addresses.
365 private final class CecMessageBuffer {
366 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
367
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900368 public boolean bufferMessage(HdmiCecMessage message) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900369 switch (message.getOpcode()) {
370 case Constants.MESSAGE_ACTIVE_SOURCE:
371 bufferActiveSource(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900372 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900373 case Constants.MESSAGE_IMAGE_VIEW_ON:
374 case Constants.MESSAGE_TEXT_VIEW_ON:
375 bufferImageOrTextViewOn(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900376 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900377 // Add here if new message that needs to buffer
378 default:
379 // Do not need to buffer messages other than above
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900380 return false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900381 }
382 }
383
384 public void processMessages() {
385 for (final HdmiCecMessage message : mBuffer) {
386 runOnServiceThread(new Runnable() {
387 @Override
388 public void run() {
389 handleCecCommand(message);
390 }
391 });
392 }
393 mBuffer.clear();
394 }
395
396 private void bufferActiveSource(HdmiCecMessage message) {
397 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
398 mBuffer.add(message);
399 }
400 }
401
402 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
403 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
404 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
405 mBuffer.add(message);
406 }
407 }
408
409 // Returns true if the message is replaced
410 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
411 for (int i = 0; i < mBuffer.size(); i++) {
412 HdmiCecMessage bufferedMessage = mBuffer.get(i);
413 if (bufferedMessage.getOpcode() == opcode) {
414 mBuffer.set(i, message);
415 return true;
416 }
417 }
418 return false;
419 }
420 }
421
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900422 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
423
424 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900425
Jungshik Jang0792d372014-04-23 17:57:26 +0900426 public HdmiControlService(Context context) {
427 super(context);
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900428 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
Jinsuk Kim50084862014-08-07 13:11:40 +0900429 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900430 }
431
Amy8027c942018-09-18 10:23:20 -0700432 protected static List<Integer> getIntList(String string) {
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900433 ArrayList<Integer> list = new ArrayList<>();
434 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
435 splitter.setString(string);
436 for (String item : splitter) {
437 try {
438 list.add(Integer.parseInt(item));
439 } catch (NumberFormatException e) {
440 Slog.w(TAG, "Can't parseInt: " + item);
441 }
442 }
443 return Collections.unmodifiableList(list);
444 }
445
Jungshik Jang0792d372014-04-23 17:57:26 +0900446 @Override
447 public void onStart() {
Amy1d0b1372018-05-24 14:36:25 -0700448 if (mIoLooper == null) {
449 mIoThread.start();
450 mIoLooper = mIoThread.getLooper();
451 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900452 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900453 mProhibitMode = false;
454 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900455 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900456
Amy1d0b1372018-05-24 14:36:25 -0700457 if (mCecController == null) {
458 mCecController = HdmiCecController.create(this);
459 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900460 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +0900461 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900462 initializeCec(INITIATED_BY_BOOT_UP);
Amy718e41e2018-08-17 17:23:37 -0700463 } else {
464 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Jungshik Janga9f10622014-07-11 15:36:39 +0900465 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900466 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900467 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900468 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900469 }
Amy4e7ff1a2018-06-07 16:24:31 -0700470 if (mMhlController == null) {
471 mMhlController = HdmiMhlControllerStub.create(this);
472 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900473 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900474 Slog.i(TAG, "Device does not support MHL-control.");
475 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900476 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900477
478 initPortInfo();
Amy1d0b1372018-05-24 14:36:25 -0700479 if (mMessageValidator == null) {
480 mMessageValidator = new HdmiCecMessageValidator(this);
481 }
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900482 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900483
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900484 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900485 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900486 IntentFilter filter = new IntentFilter();
487 filter.addAction(Intent.ACTION_SCREEN_OFF);
488 filter.addAction(Intent.ACTION_SCREEN_ON);
Rob McConnell30595562016-03-11 11:03:00 +0000489 filter.addAction(Intent.ACTION_SHUTDOWN);
Terry Heo1ca0a432014-08-18 10:30:32 +0900490 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
491 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900492
493 // Register ContentObserver to monitor the settings change.
494 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900495 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900496 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900497 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900498
Amy1d0b1372018-05-24 14:36:25 -0700499 @VisibleForTesting
500 void setCecController(HdmiCecController cecController) {
501 mCecController = cecController;
502 }
503
Amy4e7ff1a2018-06-07 16:24:31 -0700504 @VisibleForTesting
505 void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
506 mMhlController = hdmiMhlController;
507 }
508
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900509 @Override
510 public void onBootPhase(int phase) {
511 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
512 mTvInputManager = (TvInputManager) getContext().getSystemService(
513 Context.TV_INPUT_SERVICE);
Jinsuk Kime26d8332015-01-09 08:55:41 +0900514 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900515 }
516 }
517
518 TvInputManager getTvInputManager() {
519 return mTvInputManager;
520 }
521
522 void registerTvInputCallback(TvInputCallback callback) {
523 if (mTvInputManager == null) return;
524 mTvInputManager.registerCallback(callback, mHandler);
525 }
526
527 void unregisterTvInputCallback(TvInputCallback callback) {
528 if (mTvInputManager == null) return;
529 mTvInputManager.unregisterCallback(callback);
530 }
531
Jinsuk Kime26d8332015-01-09 08:55:41 +0900532 PowerManager getPowerManager() {
533 return mPowerManager;
534 }
535
Yuncheol Heo25c20292014-07-31 17:59:39 +0900536 /**
537 * Called when the initialization of local devices is complete.
538 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900539 private void onInitializeCecComplete(int initiatedBy) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900540 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
541 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
542 }
543 mWakeUpMessageReceived = false;
544
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900545 if (isTvDeviceEnabled()) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900546 mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
Yuncheol Heo0608b932014-10-13 16:39:18 +0900547 }
548 int reason = -1;
549 switch (initiatedBy) {
550 case INITIATED_BY_BOOT_UP:
551 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
552 break;
553 case INITIATED_BY_ENABLE_CEC:
554 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
555 break;
556 case INITIATED_BY_SCREEN_ON:
557 case INITIATED_BY_WAKE_UP_MESSAGE:
558 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
559 break;
560 }
561 if (reason != -1) {
562 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900563 }
564 }
565
Jinsuk Kim50084862014-08-07 13:11:40 +0900566 private void registerContentObserver() {
567 ContentResolver resolver = getContext().getContentResolver();
568 String[] settings = new String[] {
569 Global.HDMI_CONTROL_ENABLED,
570 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
571 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900572 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Jinsuk Kim50084862014-08-07 13:11:40 +0900573 Global.MHL_INPUT_SWITCHING_ENABLED,
Amy0c2e29f2018-10-23 12:17:52 -0700574 Global.MHL_POWER_CHARGE_ENABLED,
575 Global.HDMI_CEC_SWITCH_ENABLED
Jinsuk Kim50084862014-08-07 13:11:40 +0900576 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900577 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900578 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
579 UserHandle.USER_ALL);
580 }
581 }
582
583 private class SettingsObserver extends ContentObserver {
584 public SettingsObserver(Handler handler) {
585 super(handler);
586 }
587
Jungshik Jangf67113f2014-08-22 16:27:19 +0900588 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900589 @Override
590 public void onChange(boolean selfChange, Uri uri) {
591 String option = uri.getLastPathSegment();
592 boolean enabled = readBooleanSetting(option, true);
593 switch (option) {
594 case Global.HDMI_CONTROL_ENABLED:
595 setControlEnabled(enabled);
596 break;
597 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900598 if (isTvDeviceEnabled()) {
599 tv().setAutoWakeup(enabled);
600 }
Donghyun Chobc6e3722016-11-04 05:25:52 +0900601 setCecOption(OptionKey.WAKEUP, enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900602 break;
603 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900604 for (int type : mLocalDevices) {
605 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
Terry Heodd371ec2015-12-10 15:31:05 +0900606 if (localDevice != null) {
607 localDevice.setAutoDeviceOff(enabled);
608 }
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900609 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900610 // No need to propagate to HAL.
611 break;
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900612 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
613 if (isTvDeviceEnabled()) {
614 tv().setSystemAudioControlFeatureEnabled(enabled);
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900615 }
Amy79db52f2018-10-23 12:45:17 -0700616 if (isAudioSystemDevice()) {
617 audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
618 }
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900619 break;
Amy0c2e29f2018-10-23 12:17:52 -0700620 case Global.HDMI_CEC_SWITCH_ENABLED:
621 if (isAudioSystemDevice()) {
622 audioSystem().setRoutingControlFeatureEnables(enabled);
623 }
624 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900625 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900626 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900627 break;
628 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900629 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900630 break;
631 }
632 }
633 }
634
635 private static int toInt(boolean enabled) {
636 return enabled ? ENABLED : DISABLED;
637 }
638
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900639 boolean readBooleanSetting(String key, boolean defVal) {
640 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900641 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900642 }
643
644 void writeBooleanSetting(String key, boolean value) {
645 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900646 Global.putInt(cr, key, toInt(value));
647 }
648
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900649 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900650 mAddressAllocated = false;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900651 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
652 mCecController.setLanguage(mLanguage);
Yuncheol Heob5021862014-09-02 10:36:04 +0900653 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900654 }
655
Jungshik Janga5b74142014-06-23 18:03:10 +0900656 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900657 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900658 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900659 // A container for [Device type, Local device info].
660 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900661 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -0700662 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
663 && isHdmiCecNeverClaimPlaybackLogicAddr) {
664 continue;
665 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900666 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
667 if (localDevice == null) {
668 localDevice = HdmiCecLocalDevice.create(this, type);
669 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900670 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900671 localDevices.add(localDevice);
672 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900673 // It's now safe to flush existing local devices from mCecController since they were
674 // already moved to 'localDevices'.
675 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900676 allocateLogicalAddress(localDevices, initiatedBy);
677 }
678
679 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700680 @VisibleForTesting
681 protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
Yuncheol Heob5021862014-09-02 10:36:04 +0900682 final int initiatedBy) {
683 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900684 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900685 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
686 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900687 mAddressAllocated = allocatingDevices.isEmpty();
688
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900689 // For TV device, select request can be invoked while address allocation or device
690 // discovery is in progress. Initialize the request here at the start of allocation,
691 // and process the collected requests later when the allocation and device discovery
692 // is all completed.
693 mSelectRequestBuffer.clear();
694
Yuncheol Heob5021862014-09-02 10:36:04 +0900695 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
696 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900697 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
698 @Override
699 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900700 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900701 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
702 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900703 // Set POWER_STATUS_ON to all local devices because they share lifetime
704 // with system.
705 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
706 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900707 localDevice.setDeviceInfo(deviceInfo);
708 mCecController.addLocalDevice(deviceType, localDevice);
709 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900710 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900711 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900712
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900713 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900714 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900715 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900716 if (initiatedBy != INITIATED_BY_HOTPLUG) {
717 // In case of the hotplug we don't call onInitializeCecComplete()
718 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900719 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900720 }
721 notifyAddressAllocated(allocatedDevices, initiatedBy);
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900722 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900723 }
724 }
725 });
726 }
727 }
728
Jungshik Janga5b74142014-06-23 18:03:10 +0900729 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900730 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900731 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900732 for (HdmiCecLocalDevice device : devices) {
733 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900734 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900735 }
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900736 if (isTvDeviceEnabled()) {
737 tv().setSelectRequestBuffer(mSelectRequestBuffer);
738 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900739 }
740
Donghyun Chofc462b92016-05-13 21:06:02 +0900741 boolean isAddressAllocated() {
742 return mAddressAllocated;
743 }
744
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900745 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
746 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900747 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700748 @VisibleForTesting
749 protected void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900750 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900751 HdmiPortInfo[] cecPortInfo = null;
752
Amyd58d0aa2018-10-18 14:08:57 -0700753 synchronized (mLock) {
754 mPhysicalAddress = getPhysicalAddress();
755 }
756
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900757 // CEC HAL provides majority of the info while MHL does only MHL support flag for
758 // each port. Return empty array if CEC HAL didn't provide the info.
759 if (mCecController != null) {
760 cecPortInfo = mCecController.getPortInfos();
761 }
762 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900763 return;
764 }
765
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900766 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
767 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900768 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900769 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900770 portIdMap.put(info.getAddress(), info.getId());
771 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900772 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900773 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900774 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
775 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900776 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900777
Amya00e1182018-10-17 16:45:03 -0700778 if (mMhlController == null) {
779 return;
780 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900781 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
782 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
783 for (HdmiPortInfo info : mhlPortInfo) {
784 if (info.isMhlSupported()) {
785 mhlSupportedPorts.add(info.getId());
786 }
787 }
788
789 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
790 // cec port info if we do not have have port that supports MHL.
791 if (mhlSupportedPorts.isEmpty()) {
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900792 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
793 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900794 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900795 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
796 for (HdmiPortInfo info : cecPortInfo) {
797 if (mhlSupportedPorts.contains(info.getId())) {
798 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
799 info.isCecSupported(), true, info.isArcSupported()));
800 } else {
801 result.add(info);
802 }
803 }
804 mPortInfo = Collections.unmodifiableList(result);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900805 }
806
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900807 List<HdmiPortInfo> getPortInfo() {
808 return mPortInfo;
809 }
810
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900811 /**
812 * Returns HDMI port information for the given port id.
813 *
814 * @param portId HDMI port id
815 * @return {@link HdmiPortInfo} for the given port
816 */
817 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900818 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900819 }
820
Jungshik Jange9c77c82014-04-24 20:30:09 +0900821 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900822 * Returns the routing path (physical address) of the HDMI port for the given
823 * port id.
824 */
825 int portIdToPath(int portId) {
826 HdmiPortInfo portInfo = getPortInfo(portId);
827 if (portInfo == null) {
828 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900829 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900830 }
831 return portInfo.getAddress();
832 }
833
834 /**
Amya00e1182018-10-17 16:45:03 -0700835 * Returns the id of HDMI port located at the current device that runs this method.
836 *
837 * For TV with physical address 0x0000, target device 0x1120, we want port physical address
838 * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
839 * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
840 *
841 * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
842 *
843 * @param path the target device's physical address.
844 * @return the id of the port that the target device eventually connects to
845 * on the current device.
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900846 */
847 int pathToPortId(int path) {
Amya00e1182018-10-17 16:45:03 -0700848 int mask = 0xF000;
849 int finalMask = 0xF000;
850 int physicalAddress = getPhysicalAddress();
Amy3eaa85f2018-10-18 14:17:47 -0700851 int maskedAddress = physicalAddress;
852
853 while (maskedAddress != 0) {
854 maskedAddress = physicalAddress & mask;
Amya00e1182018-10-17 16:45:03 -0700855 finalMask |= mask;
Amy3eaa85f2018-10-18 14:17:47 -0700856 mask >>= 4;
Amya00e1182018-10-17 16:45:03 -0700857 }
Amy3eaa85f2018-10-18 14:17:47 -0700858
Amya00e1182018-10-17 16:45:03 -0700859 int portAddress = path & finalMask;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900860 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900861 }
862
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900863 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900864 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900865 }
866
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900867 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900868 * Returns {@link Looper} for IO operation.
869 *
870 * <p>Declared as package-private.
871 */
Amy1d0b1372018-05-24 14:36:25 -0700872 @Nullable
Jungshik Jange9c77c82014-04-24 20:30:09 +0900873 Looper getIoLooper() {
Amy1d0b1372018-05-24 14:36:25 -0700874 return mIoLooper;
875 }
876
877 @VisibleForTesting
878 void setIoLooper(Looper ioLooper) {
879 mIoLooper = ioLooper;
880 }
881
882 @VisibleForTesting
883 void setMessageValidator(HdmiCecMessageValidator messageValidator) {
884 mMessageValidator = messageValidator;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900885 }
886
887 /**
888 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
889 * for tasks that are running on main service thread.
890 *
891 * <p>Declared as package-private.
892 */
893 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900894 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900895 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900896
897 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +0900898 * Returns physical address of the device.
899 */
900 int getPhysicalAddress() {
901 return mCecController.getPhysicalAddress();
902 }
903
904 /**
905 * Returns vendor id of CEC service.
906 */
907 int getVendorId() {
908 return mCecController.getVendorId();
909 }
910
Jungshik Janga5b74142014-06-23 18:03:10 +0900911 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900912 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900913 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900914 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900915 }
916
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +0900917 @ServiceThreadOnly
918 HdmiDeviceInfo getDeviceInfoByPort(int port) {
919 assertRunOnServiceThread();
920 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
921 if (info != null) {
922 return info.getInfo();
923 }
924 return null;
925 }
926
Jungshik Jang3ee65722014-06-03 16:22:30 +0900927 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900928 * Returns version of CEC.
929 */
930 int getCecVersion() {
931 return mCecController.getVersion();
932 }
933
934 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +0900935 * Whether a device of the specified physical address is connected to ARC enabled port.
936 */
937 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900938 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900939 if (portId != Constants.INVALID_PORT_ID) {
940 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +0900941 }
942 return false;
943 }
944
Jinsuk Kim7b0cf642015-04-14 09:43:45 +0900945 @ServiceThreadOnly
946 boolean isConnected(int portId) {
947 assertRunOnServiceThread();
948 return mCecController.isConnected(portId);
949 }
950
Jungshik Jang79c58a42014-06-16 16:45:36 +0900951 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900952 mHandler.post(runnable);
953 }
954
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900955 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
956 mHandler.postAtFrontOfQueue(runnable);
957 }
958
959 private void assertRunOnServiceThread() {
960 if (Looper.myLooper() != mHandler.getLooper()) {
961 throw new IllegalStateException("Should run on service thread.");
962 }
963 }
964
Jungshik Jang67ea5212014-05-15 14:05:24 +0900965 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900966 * Transmit a CEC command to CEC bus.
967 *
968 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +0900969 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900970 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900971 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900972 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900973 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +0900974 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900975 mCecController.sendCommand(command, callback);
976 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900977 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900978 if (callback != null) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900979 callback.onSendCompleted(SendMessageResult.FAIL);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900980 }
981 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900982 }
983
Jungshik Janga5b74142014-06-23 18:03:10 +0900984 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900985 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900986 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +0900987 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900988 }
989
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900990 /**
991 * Send <Feature Abort> command on the given CEC message if possible.
992 * If the aborted message is invalid, then it wont send the message.
993 * @param command original command to be aborted
994 * @param reason reason of feature abort
995 */
996 @ServiceThreadOnly
997 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
998 assertRunOnServiceThread();
999 mCecController.maySendFeatureAbortCommand(command, reason);
1000 }
1001
Jungshik Janga5b74142014-06-23 18:03:10 +09001002 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001003 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001004 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +09001005 int errorCode = mMessageValidator.isValid(message);
1006 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +09001007 // We'll not response on the messages with the invalid source or destination
1008 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +09001009 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
1010 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
1011 }
1012 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +09001013 }
Kyeongkab.Nama15539e2018-09-14 13:55:55 +09001014
1015 if (dispatchMessageToLocalDevice(message)) {
1016 return true;
1017 }
1018
1019 return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
Jungshik Jang092b4452014-06-11 15:19:17 +09001020 }
1021
Donghyun Chobc6e3722016-11-04 05:25:52 +09001022 void enableAudioReturnChannel(int portId, boolean enabled) {
1023 mCecController.enableAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001024 }
1025
Jungshik Janga5b74142014-06-23 18:03:10 +09001026 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +09001027 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001028 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +09001029 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001030 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001031 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +09001032 return true;
1033 }
1034 }
Jungshik Jang60cffce2014-06-12 18:03:04 +09001035
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001036 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001037 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +09001038 }
Jungshik Jang092b4452014-06-11 15:19:17 +09001039 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001040 }
1041
Jungshik Jang67ea5212014-05-15 14:05:24 +09001042 /**
1043 * Called when a new hotplug event is issued.
1044 *
Jinsuk Kimed086452014-08-18 15:01:53 +09001045 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +09001046 * @param connected whether to be plugged in or not
1047 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001048 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +09001049 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +09001050 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +09001051
Amy59176da2018-10-12 16:30:54 -07001052 if (connected && !isTvDevice()
1053 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
1054 if (isSwitchDevice()) {
Amy066db152018-10-04 09:54:51 -07001055 initPortInfo();
1056 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
1057 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001058 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1059 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -07001060 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
1061 && isHdmiCecNeverClaimPlaybackLogicAddr) {
1062 continue;
1063 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001064 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
1065 if (localDevice == null) {
1066 localDevice = HdmiCecLocalDevice.create(this, type);
1067 localDevice.init();
1068 }
1069 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +09001070 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001071 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +09001072 }
Yuncheol Heob5021862014-09-02 10:36:04 +09001073
Jungshik Jang79c58a42014-06-16 16:45:36 +09001074 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +09001075 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001076 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001077 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +09001078 }
1079
Jungshik Jang02bb4262014-05-23 16:48:31 +09001080 /**
1081 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
1082 * devices.
1083 *
1084 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +09001085 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001086 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +09001087 * @param retryCount the number of retry used to send polling message to remote devices
Jakub Pawlowskib0b3a642018-12-05 10:55:57 +01001088 * @throws IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +09001089 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001090 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +09001091 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
1092 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001093 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +09001094 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
1095 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001096 }
1097
1098 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001099 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001100 if (strategy == 0) {
1101 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
1102 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001103 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001104 if (iterationStrategy == 0) {
1105 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
1106 }
1107 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +09001108 }
1109
Jungshik Jang60cffce2014-06-12 18:03:04 +09001110 List<HdmiCecLocalDevice> getAllLocalDevices() {
1111 assertRunOnServiceThread();
1112 return mCecController.getLocalDeviceList();
1113 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001114
Jungshik Jang79c58a42014-06-16 16:45:36 +09001115 Object getServiceLock() {
1116 return mLock;
1117 }
1118
1119 void setAudioStatus(boolean mute, int volume) {
Donghyun Cho65618452016-12-23 18:30:37 +09001120 if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) {
1121 return;
1122 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001123 AudioManager audioManager = getAudioManager();
1124 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
1125 if (mute) {
1126 if (!muted) {
1127 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
1128 }
1129 } else {
1130 if (muted) {
1131 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
1132 }
1133 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
1134 // volume change notification back to hdmi control service.
Shuichi.Noguchifbb50bc2017-12-06 11:12:33 +09001135 int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
1136 if (0 <= volume && volume <= 100) {
1137 Slog.i(TAG, "volume: " + volume);
1138 flag |= AudioManager.FLAG_SHOW_UI;
1139 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
1140 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001141 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001142 }
1143
Jungshik Jangea67c182014-06-19 22:19:20 +09001144 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001145 synchronized (mLock) {
1146 for (SystemAudioModeChangeListenerRecord record :
1147 mSystemAudioModeChangeListenerRecords) {
1148 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1149 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001150 }
1151 }
1152
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001153 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Jungshik Jang42c98002014-06-12 13:17:44 +09001154 // TODO: find better name instead of model name.
1155 String displayName = Build.MODEL;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001156 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +09001157 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1158 getVendorId(), displayName);
Jungshik Jang3ee65722014-06-03 16:22:30 +09001159 }
1160
Jungshik Jang7df52862014-08-11 14:35:27 +09001161 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +09001162 void handleMhlHotplugEvent(int portId, boolean connected) {
1163 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001164 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +09001165 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001166 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1167 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +09001168 if (oldDevice != null) {
1169 oldDevice.onDeviceRemoved();
1170 Slog.i(TAG, "Old device of port " + portId + " is removed");
1171 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001172 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1173 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001174 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001175 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001176 if (device != null) {
1177 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001178 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1179 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001180 } else {
1181 Slog.w(TAG, "No device to remove:[portId=" + portId);
1182 }
1183 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001184 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001185 }
1186
1187 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001188 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001189 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001190 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001191 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001192 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001193 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001194 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1195 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001196 }
1197 }
1198
1199 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001200 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001201 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001202 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001203 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001204 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001205 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001206 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001207 }
1208 }
1209
1210 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001211 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001212 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001213 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001214
Jungshik Jang7df52862014-08-11 14:35:27 +09001215 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001216 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001217 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001218 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001219 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1220 }
1221 }
1222
Jinsuk Kimed086452014-08-18 15:01:53 +09001223 @ServiceThreadOnly
1224 private void updateSafeMhlInput() {
1225 assertRunOnServiceThread();
1226 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001227 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001228 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001229 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001230 HdmiDeviceInfo info = device.getInfo();
1231 if (info != null) {
1232 if (inputs.isEmpty()) {
1233 inputs = new ArrayList<>();
1234 }
1235 inputs.add(device.getInfo());
1236 }
1237 }
1238 synchronized (mLock) {
1239 mMhlDevices = inputs;
1240 }
1241 }
1242
Andreas Gampea36dc622018-02-05 17:19:22 -08001243 @GuardedBy("mLock")
Jinsuk Kimed086452014-08-18 15:01:53 +09001244 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1245 return mMhlDevices;
1246 }
1247
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001248 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1249 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001250
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001251 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001252 mListener = listener;
1253 }
1254
1255 @Override
1256 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001257 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001258 }
1259 }
1260
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001261 // Record class that monitors the event of the caller of being killed. Used to clean up
1262 // the listener list and record list accordingly.
1263 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1264 private final IHdmiHotplugEventListener mListener;
1265
1266 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1267 mListener = listener;
1268 }
1269
1270 @Override
1271 public void binderDied() {
1272 synchronized (mLock) {
1273 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001274 }
1275 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001276
1277 @Override
1278 public boolean equals(Object obj) {
1279 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1280 if (obj == this) return true;
1281 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1282 return other.mListener == this.mListener;
1283 }
1284
1285 @Override
1286 public int hashCode() {
1287 return mListener.hashCode();
1288 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001289 }
1290
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001291 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1292 private final IHdmiDeviceEventListener mListener;
1293
1294 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1295 mListener = listener;
1296 }
1297
1298 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001299 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001300 synchronized (mLock) {
1301 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001302 }
1303 }
1304 }
1305
Jungshik Jangea67c182014-06-19 22:19:20 +09001306 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001307 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001308
1309 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1310 mListener = listener;
1311 }
1312
1313 @Override
1314 public void binderDied() {
1315 synchronized (mLock) {
1316 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001317 }
1318 }
1319 }
1320
Jinsuk Kim119160a2014-07-07 18:48:10 +09001321 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1322 private final IHdmiVendorCommandListener mListener;
1323 private final int mDeviceType;
1324
1325 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1326 mListener = listener;
1327 mDeviceType = deviceType;
1328 }
1329
1330 @Override
1331 public void binderDied() {
1332 synchronized (mLock) {
1333 mVendorCommandListenerRecords.remove(this);
1334 }
1335 }
1336 }
1337
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001338 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001339 private final IHdmiRecordListener mListener;
1340
1341 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1342 mListener = listener;
1343 }
1344
Jungshik Jangb6591b82014-07-23 16:10:23 +09001345 @Override
1346 public void binderDied() {
1347 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001348 if (mRecordListenerRecord == this) {
1349 mRecordListenerRecord = null;
1350 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001351 }
1352 }
1353 }
1354
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001355 private void enforceAccessPermission() {
1356 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1357 }
1358
1359 private final class BinderService extends IHdmiControlService.Stub {
1360 @Override
1361 public int[] getSupportedTypes() {
1362 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001363 // mLocalDevices is an unmodifiable list - no lock necesary.
1364 int[] localDevices = new int[mLocalDevices.size()];
1365 for (int i = 0; i < localDevices.length; ++i) {
1366 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001367 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001368 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001369 }
1370
1371 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001372 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001373 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001374 HdmiCecLocalDeviceTv tv = tv();
1375 if (tv == null) {
1376 Slog.w(TAG, "Local tv device not available");
1377 return null;
1378 }
1379 ActiveSource activeSource = tv.getActiveSource();
1380 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001381 return new HdmiDeviceInfo(activeSource.logicalAddress,
1382 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1383 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001384 }
1385 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001386 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001387 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001388 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001389 }
1390 return null;
1391 }
1392
1393 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001394 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001395 enforceAccessPermission();
1396 runOnServiceThread(new Runnable() {
1397 @Override
1398 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001399 if (callback == null) {
1400 Slog.e(TAG, "Callback cannot be null");
1401 return;
1402 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001403 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001404 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001405 if (!mAddressAllocated) {
1406 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1407 HdmiControlService.this, deviceId, callback));
1408 return;
1409 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001410 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001411 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001412 return;
1413 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001414 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001415 if (device != null) {
1416 if (device.getPortId() == tv.getActivePortId()) {
1417 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001418 return;
1419 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001420 // Upon selecting MHL device, we send RAP[Content On] to wake up
1421 // the connected mobile device, start routing control to switch ports.
1422 // callback is handled by MHL action.
1423 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001424 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001425 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001426 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001427 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001428 }
1429 });
1430 }
1431
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001432 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001433 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1434 enforceAccessPermission();
1435 runOnServiceThread(new Runnable() {
1436 @Override
1437 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001438 if (callback == null) {
1439 Slog.e(TAG, "Callback cannot be null");
1440 return;
1441 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001442 HdmiCecLocalDeviceTv tv = tv();
shubangd932cb52018-09-14 17:55:16 -07001443 if (tv != null) {
1444 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001445 return;
1446 }
shubangd932cb52018-09-14 17:55:16 -07001447 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1448 if (audioSystem != null) {
1449 audioSystem.doManualPortSwitching(portId, callback);
1450 return;
1451 }
1452
1453 if (!mAddressAllocated) {
1454 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1455 HdmiControlService.this, portId, callback));
1456 return;
1457 }
1458 Slog.w(TAG, "Local device not available");
1459 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1460 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001461 }
1462 });
1463 }
1464
1465 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001466 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001467 enforceAccessPermission();
1468 runOnServiceThread(new Runnable() {
1469 @Override
1470 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001471 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001472 if (device != null) {
1473 device.sendKeyEvent(keyCode, isPressed);
1474 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001475 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001476 if (mCecController != null) {
1477 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1478 if (localDevice == null) {
1479 Slog.w(TAG, "Local device not available");
1480 return;
1481 }
1482 localDevice.sendKeyEvent(keyCode, isPressed);
1483 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001484 }
1485 });
1486 }
1487
1488 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001489 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001490 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001491 runOnServiceThread(new Runnable() {
1492 @Override
1493 public void run() {
1494 HdmiControlService.this.oneTouchPlay(callback);
1495 }
1496 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001497 }
1498
1499 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001500 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001501 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001502 runOnServiceThread(new Runnable() {
1503 @Override
1504 public void run() {
1505 HdmiControlService.this.queryDisplayStatus(callback);
1506 }
1507 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001508 }
1509
1510 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001511 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001512 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001513 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001514 }
1515
1516 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001517 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001518 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001519 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001520 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001521
1522 @Override
1523 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1524 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001525 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001526 }
1527
1528 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001529 public List<HdmiPortInfo> getPortInfo() {
1530 enforceAccessPermission();
Jungshik Jang2738e2d2014-08-19 09:30:05 +09001531 return HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001532 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001533
1534 @Override
1535 public boolean canChangeSystemAudioMode() {
1536 enforceAccessPermission();
1537 HdmiCecLocalDeviceTv tv = tv();
1538 if (tv == null) {
1539 return false;
1540 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001541 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001542 }
1543
1544 @Override
1545 public boolean getSystemAudioMode() {
Shubang Lu00b976a2018-08-01 18:11:46 -07001546 // TODO(shubang): handle getSystemAudioMode() for all device types
Jungshik Jangea67c182014-06-19 22:19:20 +09001547 enforceAccessPermission();
1548 HdmiCecLocalDeviceTv tv = tv();
Shubang Lu00b976a2018-08-01 18:11:46 -07001549 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1550 return (tv != null && tv.isSystemAudioActivated())
1551 || (audioSystem != null && audioSystem.isSystemAudioActivated());
Jungshik Jangea67c182014-06-19 22:19:20 +09001552 }
1553
1554 @Override
Amyd58d0aa2018-10-18 14:08:57 -07001555 public int getPhysicalAddress() {
1556 enforceAccessPermission();
1557 synchronized (mLock) {
1558 return mPhysicalAddress;
1559 }
1560 }
1561
1562 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001563 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1564 enforceAccessPermission();
1565 runOnServiceThread(new Runnable() {
1566 @Override
1567 public void run() {
1568 HdmiCecLocalDeviceTv tv = tv();
1569 if (tv == null) {
1570 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001571 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001572 return;
1573 }
1574 tv.changeSystemAudioMode(enabled, callback);
1575 }
1576 });
1577 }
1578
1579 @Override
1580 public void addSystemAudioModeChangeListener(
1581 final IHdmiSystemAudioModeChangeListener listener) {
1582 enforceAccessPermission();
1583 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1584 }
1585
1586 @Override
1587 public void removeSystemAudioModeChangeListener(
1588 final IHdmiSystemAudioModeChangeListener listener) {
1589 enforceAccessPermission();
1590 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1591 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001592
1593 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001594 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1595 enforceAccessPermission();
1596 HdmiControlService.this.setInputChangeListener(listener);
1597 }
1598
1599 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001600 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001601 enforceAccessPermission();
1602 // No need to hold the lock for obtaining TV device as the local device instance
1603 // is preserved while the HDMI control is enabled.
1604 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001605 synchronized (mLock) {
1606 List<HdmiDeviceInfo> cecDevices = (tv == null)
1607 ? Collections.<HdmiDeviceInfo>emptyList()
1608 : tv.getSafeExternalInputsLocked();
1609 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001610 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001611 }
1612
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001613 // Returns all the CEC devices on the bus including system audio, switch,
1614 // even those of reserved type.
1615 @Override
1616 public List<HdmiDeviceInfo> getDeviceList() {
1617 enforceAccessPermission();
1618 HdmiCecLocalDeviceTv tv = tv();
Amy6f031af2018-10-30 16:38:33 -07001619 if (tv != null) {
1620 synchronized (mLock) {
1621 return tv.getSafeCecDevicesLocked();
1622 }
1623 } else {
1624 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1625 synchronized (mLock) {
1626 return (audioSystem == null)
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001627 ? Collections.<HdmiDeviceInfo>emptyList()
Amy6f031af2018-10-30 16:38:33 -07001628 : audioSystem.getSafeCecDevicesLocked();
1629 }
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001630 }
1631 }
1632
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001633 @Override
Amy6f031af2018-10-30 16:38:33 -07001634 public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
1635 enforceAccessPermission();
1636 runOnServiceThread(new Runnable() {
1637 @Override
1638 public void run() {
1639 if (powerStatus == HdmiControlManager.POWER_STATUS_ON
1640 || powerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
1641 sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1642 getRemoteControlSourceAddress(), logicalAddress));
1643 } else {
1644 Slog.w(TAG, "Device " + logicalAddress + " is already off " + powerStatus);
1645 }
1646 }
1647 });
1648 }
1649
1650 @Override
1651 public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
1652 // TODO(amyjojo): implement the method
1653 }
1654
1655 @Override
1656 // TODO(AMYJOJO): add a result callback
1657 public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
1658 enforceAccessPermission();
1659 runOnServiceThread(new Runnable() {
1660 @Override
1661 public void run() {
1662 HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
1663 getRemoteControlSourceAddress(), physicalAddress);
1664 if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
1665 if (getSwitchDevice() != null) {
1666 getSwitchDevice().handleSetStreamPath(setStreamPath);
1667 } else {
1668 Slog.e(TAG, "Can't get the correct local device to handle routing.");
1669 }
1670 } else {
1671 sendCecCommand(setStreamPath);
1672 }
1673 }
1674 });
1675 }
1676
1677 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001678 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1679 final int maxIndex) {
1680 enforceAccessPermission();
1681 runOnServiceThread(new Runnable() {
1682 @Override
1683 public void run() {
1684 HdmiCecLocalDeviceTv tv = tv();
1685 if (tv == null) {
1686 Slog.w(TAG, "Local tv device not available");
1687 return;
1688 }
1689 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1690 }
1691 });
1692 }
1693
1694 @Override
1695 public void setSystemAudioMute(final boolean mute) {
1696 enforceAccessPermission();
1697 runOnServiceThread(new Runnable() {
1698 @Override
1699 public void run() {
1700 HdmiCecLocalDeviceTv tv = tv();
1701 if (tv == null) {
1702 Slog.w(TAG, "Local tv device not available");
1703 return;
1704 }
1705 tv.changeMute(mute);
1706 }
1707 });
1708 }
1709
1710 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001711 public void setArcMode(final boolean enabled) {
1712 enforceAccessPermission();
1713 runOnServiceThread(new Runnable() {
1714 @Override
1715 public void run() {
1716 HdmiCecLocalDeviceTv tv = tv();
1717 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001718 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001719 return;
1720 }
1721 }
1722 });
1723 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001724
1725 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001726 public void setProhibitMode(final boolean enabled) {
1727 enforceAccessPermission();
1728 if (!isTvDevice()) {
1729 return;
1730 }
1731 HdmiControlService.this.setProhibitMode(enabled);
1732 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001733
1734 @Override
1735 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1736 final int deviceType) {
1737 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001738 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001739 }
1740
1741 @Override
1742 public void sendVendorCommand(final int deviceType, final int targetAddress,
1743 final byte[] params, final boolean hasVendorId) {
1744 enforceAccessPermission();
1745 runOnServiceThread(new Runnable() {
1746 @Override
1747 public void run() {
1748 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1749 if (device == null) {
1750 Slog.w(TAG, "Local device not available");
1751 return;
1752 }
1753 if (hasVendorId) {
1754 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1755 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1756 getVendorId(), params));
1757 } else {
1758 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1759 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1760 }
1761 }
1762 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001763 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001764
1765 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001766 public void sendStandby(final int deviceType, final int deviceId) {
1767 enforceAccessPermission();
1768 runOnServiceThread(new Runnable() {
1769 @Override
1770 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001771 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1772 if (mhlDevice != null) {
1773 mhlDevice.sendStandby();
1774 return;
1775 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001776 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1777 if (device == null) {
Amy777abd72018-09-10 16:11:33 -07001778 device = audioSystem();
1779 }
1780 if (device == null) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001781 Slog.w(TAG, "Local device not available");
1782 return;
1783 }
1784 device.sendStandby(deviceId);
1785 }
1786 });
1787 }
1788
1789 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001790 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001791 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001792 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001793 }
1794
1795 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001796 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001797 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001798 runOnServiceThread(new Runnable() {
1799 @Override
1800 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001801 if (!isTvDeviceEnabled()) {
1802 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001803 return;
1804 }
1805 tv().startOneTouchRecord(recorderAddress, recordSource);
1806 }
1807 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001808 }
1809
1810 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001811 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001812 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001813 runOnServiceThread(new Runnable() {
1814 @Override
1815 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001816 if (!isTvDeviceEnabled()) {
1817 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001818 return;
1819 }
1820 tv().stopOneTouchRecord(recorderAddress);
1821 }
1822 });
1823 }
1824
1825 @Override
1826 public void startTimerRecording(final int recorderAddress, final int sourceType,
1827 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001828 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001829 runOnServiceThread(new Runnable() {
1830 @Override
1831 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001832 if (!isTvDeviceEnabled()) {
1833 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001834 return;
1835 }
1836 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1837 }
1838 });
1839 }
1840
1841 @Override
1842 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1843 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001844 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001845 runOnServiceThread(new Runnable() {
1846 @Override
1847 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001848 if (!isTvDeviceEnabled()) {
1849 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001850 return;
1851 }
1852 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1853 }
1854 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001855 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001856
1857 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001858 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09001859 final byte[] data) {
1860 enforceAccessPermission();
1861 runOnServiceThread(new Runnable() {
1862 @Override
1863 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09001864 if (!isControlEnabled()) {
1865 Slog.w(TAG, "Hdmi control is disabled.");
1866 return ;
1867 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001868 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09001869 if (device == null) {
1870 Slog.w(TAG, "Invalid port id:" + portId);
1871 return;
1872 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001873 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09001874 }
1875 });
1876 }
1877
1878 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001879 public void addHdmiMhlVendorCommandListener(
1880 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001881 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001882 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09001883 }
Terry Heo959d2db2014-08-28 16:45:41 +09001884
1885 @Override
Donghyun Chob3515642017-03-02 13:47:40 +09001886 public void setStandbyMode(final boolean isStandbyModeOn) {
1887 enforceAccessPermission();
1888 runOnServiceThread(new Runnable() {
1889 @Override
1890 public void run() {
1891 HdmiControlService.this.setStandbyMode(isStandbyModeOn);
1892 }
1893 });
1894 }
1895
1896 @Override
Shubangc480a712018-06-11 18:02:42 -07001897 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
1898 final boolean isMute) {
1899 enforceAccessPermission();
1900 runOnServiceThread(new Runnable() {
1901 @Override
1902 public void run() {
1903 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1904 if (device == null) {
1905 Slog.w(TAG, "Local device not available");
1906 return;
1907 }
1908 if (audioSystem() == null) {
1909 Slog.w(TAG, "audio system is not available");
1910 return;
1911 }
Shubang17b29342018-07-19 17:58:48 -07001912 if (!audioSystem().isSystemAudioActivated()) {
Shubangc480a712018-06-11 18:02:42 -07001913 Slog.w(TAG, "audio system is not in system audio mode");
1914 return;
1915 }
Nick Chalko01b979c2018-10-19 14:54:30 -07001916 audioSystem().reportAudioStatus(Constants.ADDR_TV);
Shubangc480a712018-06-11 18:02:42 -07001917 }
1918 });
1919 }
1920
1921 @Override
Amy4ad4e782018-10-17 17:48:49 -07001922 public void setSystemAudioModeOnForAudioOnlySource() {
1923 enforceAccessPermission();
1924 runOnServiceThread(new Runnable() {
1925 @Override
1926 public void run() {
1927 if (!isAudioSystemDevice()) {
1928 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
1929 return;
1930 }
1931 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
1932 Slog.e(TAG, "System Audio Mode is not supported.");
1933 return;
1934 }
1935 sendCecCommand(
1936 HdmiCecMessageBuilder.buildSetSystemAudioMode(
1937 audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
1938 }
1939 });
1940 }
1941
1942 @Override
Terry Heo959d2db2014-08-28 16:45:41 +09001943 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06001944 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
Terry Heo959d2db2014-08-28 16:45:41 +09001945 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
1946
Terry Heo959d2db2014-08-28 16:45:41 +09001947 pw.println("mProhibitMode: " + mProhibitMode);
Nick Chalkob9e48e22018-10-23 06:59:39 -07001948 pw.println("mPowerStatus: " + mPowerStatus);
1949
1950 // System settings
1951 pw.println("System_settings:");
1952 pw.increaseIndent();
1953 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1954 pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
1955 pw.decreaseIndent();
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001956
1957 pw.println("mMhlController: ");
1958 pw.increaseIndent();
1959 mMhlController.dump(pw);
1960 pw.decreaseIndent();
1961
Nick Chalkob9e48e22018-10-23 06:59:39 -07001962 HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
1963 if (mCecController != null) {
1964 pw.println("mCecController: ");
1965 pw.increaseIndent();
1966 mCecController.dump(pw);
1967 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +09001968 }
Terry Heo959d2db2014-08-28 16:45:41 +09001969 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001970 }
1971
Amy6f031af2018-10-30 16:38:33 -07001972 // Get the source address to send out commands to devices connected to the current device
1973 // when other services interact with HdmiControlService.
1974 private int getRemoteControlSourceAddress() {
1975 if (isAudioSystemDevice()) {
1976 return audioSystem().getDeviceInfo().getLogicalAddress();
1977 } else if (isPlaybackDevice()) {
1978 return playback().getDeviceInfo().getLogicalAddress();
1979 }
1980 return ADDR_UNREGISTERED;
1981 }
1982
1983 // Get the switch device to do CEC routing control
1984 @Nullable
1985 private HdmiCecLocalDeviceSource getSwitchDevice() {
1986 if (isAudioSystemDevice()) {
1987 return audioSystem();
1988 }
1989 if (isPlaybackDevice()) {
1990 return playback();
1991 }
1992 return null;
1993 }
1994
Jungshik Janga5b74142014-06-23 18:03:10 +09001995 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001996 private void oneTouchPlay(final IHdmiControlCallback callback) {
1997 assertRunOnServiceThread();
Amy848a9f22018-08-27 17:21:26 -07001998 HdmiCecLocalDeviceSource source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001999 if (source == null) {
Amy848a9f22018-08-27 17:21:26 -07002000 source = audioSystem();
2001 }
2002
2003 if (source == null) {
2004 Slog.w(TAG, "Local source device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002005 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002006 return;
2007 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002008 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002009 }
2010
Jungshik Janga5b74142014-06-23 18:03:10 +09002011 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002012 private void queryDisplayStatus(final IHdmiControlCallback callback) {
2013 assertRunOnServiceThread();
2014 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002015 if (source == null) {
2016 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002017 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002018 return;
2019 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002020 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002021 }
2022
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002023 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
2024 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002025 try {
2026 listener.asBinder().linkToDeath(record, 0);
2027 } catch (RemoteException e) {
2028 Slog.w(TAG, "Listener already died");
2029 return;
2030 }
2031 synchronized (mLock) {
2032 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002033 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002034
2035 // Inform the listener of the initial state of each HDMI port by generating
2036 // hotplug events.
2037 runOnServiceThread(new Runnable() {
2038 @Override
2039 public void run() {
2040 synchronized (mLock) {
2041 if (!mHotplugEventListenerRecords.contains(record)) return;
2042 }
2043 for (HdmiPortInfo port : mPortInfo) {
2044 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
2045 mCecController.isConnected(port.getId()));
2046 synchronized (mLock) {
2047 invokeHotplugEventListenerLocked(listener, event);
2048 }
2049 }
2050 }
2051 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002052 }
2053
2054 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
2055 synchronized (mLock) {
2056 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2057 if (record.mListener.asBinder() == listener.asBinder()) {
2058 listener.asBinder().unlinkToDeath(record, 0);
2059 mHotplugEventListenerRecords.remove(record);
2060 break;
2061 }
2062 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002063 }
2064 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002065
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002066 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002067 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
2068 try {
2069 listener.asBinder().linkToDeath(record, 0);
2070 } catch (RemoteException e) {
2071 Slog.w(TAG, "Listener already died");
2072 return;
2073 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002074 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002075 mDeviceEventListenerRecords.add(record);
2076 }
2077 }
2078
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002079 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002080 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002081 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002082 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002083 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002084 } catch (RemoteException e) {
2085 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002086 }
2087 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002088 }
2089 }
2090
Jungshik Jangea67c182014-06-19 22:19:20 +09002091 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
2092 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
2093 listener);
2094 try {
2095 listener.asBinder().linkToDeath(record, 0);
2096 } catch (RemoteException e) {
2097 Slog.w(TAG, "Listener already died");
2098 return;
2099 }
2100 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09002101 mSystemAudioModeChangeListenerRecords.add(record);
2102 }
2103 }
2104
2105 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
2106 synchronized (mLock) {
2107 for (SystemAudioModeChangeListenerRecord record :
2108 mSystemAudioModeChangeListenerRecords) {
2109 if (record.mListener.asBinder() == listener) {
2110 listener.asBinder().unlinkToDeath(record, 0);
2111 mSystemAudioModeChangeListenerRecords.remove(record);
2112 break;
2113 }
2114 }
Jungshik Jangea67c182014-06-19 22:19:20 +09002115 }
2116 }
2117
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002118 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09002119 private final IHdmiInputChangeListener mListener;
2120
2121 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
2122 mListener = listener;
2123 }
2124
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002125 @Override
2126 public void binderDied() {
2127 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09002128 if (mInputChangeListenerRecord == this) {
2129 mInputChangeListenerRecord = null;
2130 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002131 }
2132 }
2133 }
2134
2135 private void setInputChangeListener(IHdmiInputChangeListener listener) {
2136 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002137 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002138 try {
2139 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
2140 } catch (RemoteException e) {
2141 Slog.w(TAG, "Listener already died");
2142 return;
2143 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002144 }
2145 }
2146
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002147 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002148 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002149 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002150 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002151 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002152 } catch (RemoteException e) {
2153 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
2154 }
2155 }
2156 }
2157 }
2158
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002159 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09002160 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002161 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002162 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002163 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002164 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002165 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002166 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002167 }
2168 }
2169
2170 byte[] invokeRecordRequestListener(int recorderAddress) {
2171 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002172 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002173 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002174 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002175 } catch (RemoteException e) {
2176 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002177 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002178 }
2179 return EmptyArray.BYTE;
2180 }
2181 }
2182
Jungshik Jang326aef02014-11-05 12:50:35 +09002183 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002184 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002185 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002186 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002187 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002188 } catch (RemoteException e) {
2189 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
2190 }
2191 }
2192 }
2193 }
2194
Jungshik Jang326aef02014-11-05 12:50:35 +09002195 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002196 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002197 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002198 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002199 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002200 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002201 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
2202 }
2203 }
2204 }
2205 }
2206
Jungshik Jang326aef02014-11-05 12:50:35 +09002207 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002208 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002209 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002210 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002211 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
2212 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09002213 } catch (RemoteException e) {
2214 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002215 }
2216 }
2217 }
2218 }
2219
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002220 private void invokeCallback(IHdmiControlCallback callback, int result) {
2221 try {
2222 callback.onComplete(result);
2223 } catch (RemoteException e) {
2224 Slog.e(TAG, "Invoking callback failed:" + e);
2225 }
2226 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09002227
Jungshik Jangf4249322014-08-21 14:17:05 +09002228 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09002229 boolean enabled) {
2230 try {
2231 listener.onStatusChanged(enabled);
2232 } catch (RemoteException e) {
2233 Slog.e(TAG, "Invoking callback failed:" + e);
2234 }
2235 }
2236
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002237 private void announceHotplugEvent(int portId, boolean connected) {
2238 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002239 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002240 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2241 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002242 }
2243 }
2244 }
2245
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002246 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09002247 HdmiHotplugEvent event) {
2248 try {
2249 listener.onReceived(event);
2250 } catch (RemoteException e) {
2251 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
2252 }
Jungshik Jange81e1082014-06-05 15:37:59 +09002253 }
2254
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09002255 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002256 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09002257 }
2258
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002259 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09002260 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002261 }
2262
Amyb887fa02018-06-21 11:22:13 -07002263 boolean isAudioSystemDevice() {
2264 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2265 }
2266
Amy34037422018-09-06 13:21:08 -07002267 boolean isPlaybackDevice() {
2268 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
2269 }
2270
Amy066db152018-10-04 09:54:51 -07002271 boolean isSwitchDevice() {
2272 return SystemProperties.getBoolean(
Amy17ee20f2018-10-11 11:08:23 -07002273 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Amy066db152018-10-04 09:54:51 -07002274 }
2275
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002276 boolean isTvDeviceEnabled() {
2277 return isTvDevice() && tv() != null;
2278 }
2279
Amy34037422018-09-06 13:21:08 -07002280 protected HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002281 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002282 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002283 }
Jungshik Janga858d222014-06-23 17:17:47 +09002284
Shubangc480a712018-06-11 18:02:42 -07002285 public HdmiCecLocalDeviceAudioSystem audioSystem() {
2286 return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
2287 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2288 }
2289
Jungshik Janga858d222014-06-23 17:17:47 +09002290 AudioManager getAudioManager() {
2291 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
2292 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09002293
2294 boolean isControlEnabled() {
2295 synchronized (mLock) {
2296 return mHdmiControlEnabled;
2297 }
2298 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002299
Jungshik Jangf67113f2014-08-22 16:27:19 +09002300 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002301 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002302 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002303 return mPowerStatus;
2304 }
2305
Jungshik Jangf67113f2014-08-22 16:27:19 +09002306 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002307 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002308 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002309 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2310 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002311 }
2312
Jungshik Jangf67113f2014-08-22 16:27:19 +09002313 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002314 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002315 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002316 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2317 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002318 }
2319
Jungshik Jangf67113f2014-08-22 16:27:19 +09002320 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002321 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002322 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002323 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002324 }
2325
2326 @ServiceThreadOnly
2327 void wakeUp() {
2328 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002329 mWakeUpMessageReceived = true;
Dianne Hackborn280a64e2015-07-13 14:48:08 -07002330 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002331 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2332 // the intent, the sequence will continue at onWakeUp().
2333 }
2334
2335 @ServiceThreadOnly
2336 void standby() {
2337 assertRunOnServiceThread();
Donghyun Cho02920a02016-10-11 17:17:34 +09002338 if (!canGoToStandby()) {
2339 return;
2340 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002341 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002342 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002343 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2344 // the intent, the sequence will continue at onStandby().
2345 }
2346
Donghyun Choafd26a22016-12-23 15:53:28 +09002347 boolean isWakeUpMessageReceived() {
2348 return mWakeUpMessageReceived;
2349 }
2350
Amybf8a4662018-07-02 12:34:24 -07002351 @VisibleForTesting
2352 boolean isStandbyMessageReceived() {
2353 return mStandbyMessageReceived;
2354 }
2355
Yuncheol Heo38db6292014-07-01 14:15:14 +09002356 @ServiceThreadOnly
2357 private void onWakeUp() {
2358 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002359 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002360 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002361 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002362 int startReason = INITIATED_BY_SCREEN_ON;
2363 if (mWakeUpMessageReceived) {
2364 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2365 }
2366 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002367 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002368 } else {
2369 Slog.i(TAG, "Device does not support HDMI-CEC.");
2370 }
2371 // TODO: Initialize MHL local devices.
2372 }
2373
2374 @ServiceThreadOnly
Amybf8a4662018-07-02 12:34:24 -07002375 @VisibleForTesting
2376 protected void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002377 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002378 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002379 invokeVendorCommandListenersOnControlStateChanged(false,
2380 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Amybf8a4662018-07-02 12:34:24 -07002381
2382 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2383
2384 if (!isStandbyMessageReceived() && !canGoToStandby()) {
Donghyun Cho02920a02016-10-11 17:17:34 +09002385 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Amybf8a4662018-07-02 12:34:24 -07002386 for (HdmiCecLocalDevice device : devices) {
2387 device.onStandby(mStandbyMessageReceived, standbyAction);
2388 }
Donghyun Cho02920a02016-10-11 17:17:34 +09002389 return;
2390 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002391
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002392 disableDevices(new PendingActionClearedCallback() {
2393 @Override
2394 public void onCleared(HdmiCecLocalDevice device) {
2395 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2396 devices.remove(device);
2397 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002398 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002399 // We will not clear local devices here, since some OEM/SOC will keep passing
2400 // the received packets until the application processor enters to the sleep
2401 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002402 }
2403 }
2404 });
2405 }
2406
Jinsuk Kime26d8332015-01-09 08:55:41 +09002407 private boolean canGoToStandby() {
2408 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2409 if (!device.canGoToStandby()) return false;
2410 }
2411 return true;
2412 }
2413
Terry Heo1ca0a432014-08-18 10:30:32 +09002414 @ServiceThreadOnly
2415 private void onLanguageChanged(String language) {
2416 assertRunOnServiceThread();
2417 mLanguage = language;
2418
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002419 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002420 tv().broadcastMenuLanguage(language);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002421 mCecController.setLanguage(language);
Terry Heo1ca0a432014-08-18 10:30:32 +09002422 }
2423 }
2424
Jungshik Jangf67113f2014-08-22 16:27:19 +09002425 @ServiceThreadOnly
2426 String getLanguage() {
2427 assertRunOnServiceThread();
2428 return mLanguage;
2429 }
2430
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002431 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002432 if (mCecController != null) {
2433 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2434 device.disableDevice(mStandbyMessageReceived, callback);
2435 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002436 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002437
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002438 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002439 }
2440
Yuncheol Heo38db6292014-07-01 14:15:14 +09002441 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002442 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002443 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002444 if (mCecController == null) {
2445 return;
2446 }
2447 mCecController.clearLogicalAddress();
2448 mCecController.clearLocalDevices();
2449 }
2450
2451 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002452 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002453 assertRunOnServiceThread();
2454 Slog.v(TAG, "onStandbyCompleted");
2455
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002456 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002457 return;
2458 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002459 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002460 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002461 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002462 }
2463 mStandbyMessageReceived = false;
Amyb887fa02018-06-21 11:22:13 -07002464 if (!isAudioSystemDevice()) {
2465 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2466 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2467 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002468 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002469
Jinsuk Kim119160a2014-07-07 18:48:10 +09002470 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2471 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2472 try {
2473 listener.asBinder().linkToDeath(record, 0);
2474 } catch (RemoteException e) {
2475 Slog.w(TAG, "Listener already died");
2476 return;
2477 }
2478 synchronized (mLock) {
2479 mVendorCommandListenerRecords.add(record);
2480 }
2481 }
2482
Yuncheol Heo0608b932014-10-13 16:39:18 +09002483 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2484 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002485 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002486 if (mVendorCommandListenerRecords.isEmpty()) {
2487 return false;
2488 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002489 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2490 if (record.mDeviceType != deviceType) {
2491 continue;
2492 }
2493 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002494 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002495 } catch (RemoteException e) {
2496 Slog.e(TAG, "Failed to notify vendor command reception", e);
2497 }
2498 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002499 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002500 }
2501 }
2502
Yuncheol Heo0608b932014-10-13 16:39:18 +09002503 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2504 synchronized (mLock) {
2505 if (mVendorCommandListenerRecords.isEmpty()) {
2506 return false;
2507 }
2508 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2509 try {
2510 record.mListener.onControlStateChanged(enabled, reason);
2511 } catch (RemoteException e) {
2512 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2513 }
2514 }
2515 return true;
2516 }
2517 }
2518
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002519 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2520 HdmiMhlVendorCommandListenerRecord record =
2521 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002522 try {
2523 listener.asBinder().linkToDeath(record, 0);
2524 } catch (RemoteException e) {
2525 Slog.w(TAG, "Listener already died.");
2526 return;
2527 }
2528
2529 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002530 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002531 }
2532 }
2533
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002534 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002535 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002536 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002537 try {
2538 record.mListener.onReceived(portId, offest, length, data);
2539 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002540 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002541 }
2542 }
2543 }
2544 }
2545
Donghyun Chob3515642017-03-02 13:47:40 +09002546 void setStandbyMode(boolean isStandbyModeOn) {
2547 assertRunOnServiceThread();
2548 if (isPowerOnOrTransient() && isStandbyModeOn) {
2549 mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2550 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2551 if (playback() != null) {
2552 playback().sendStandby(0 /* unused */);
2553 }
2554 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
2555 mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2556 if (playback() != null) {
2557 oneTouchPlay(new IHdmiControlCallback.Stub() {
2558 @Override
2559 public void onComplete(int result) {
2560 if (result != HdmiControlManager.RESULT_SUCCESS) {
2561 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
2562 }
2563 }
2564 });
2565 }
2566 }
2567 }
2568
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002569 boolean isProhibitMode() {
2570 synchronized (mLock) {
2571 return mProhibitMode;
2572 }
2573 }
2574
2575 void setProhibitMode(boolean enabled) {
2576 synchronized (mLock) {
2577 mProhibitMode = enabled;
2578 }
2579 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002580
2581 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +09002582 void setCecOption(int key, boolean value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002583 assertRunOnServiceThread();
2584 mCecController.setOption(key, value);
2585 }
2586
2587 @ServiceThreadOnly
2588 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002589 assertRunOnServiceThread();
2590
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002591 synchronized (mLock) {
2592 mHdmiControlEnabled = enabled;
2593 }
2594
2595 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002596 enableHdmiControlService();
2597 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002598 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002599 // Call the vendor handler before the service is disabled.
2600 invokeVendorCommandListenersOnControlStateChanged(false,
2601 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2602 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2603 // a chance to run.
2604 runOnServiceThread(new Runnable() {
2605 @Override
2606 public void run() {
2607 disableHdmiControlService();
2608 }
2609 });
2610 return;
2611 }
2612
2613 @ServiceThreadOnly
2614 private void enableHdmiControlService() {
Amy718e41e2018-08-17 17:23:37 -07002615 mCecController.setOption(OptionKey.ENABLE_CEC, true);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002616 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Yuncheol Heof1702482014-11-27 19:52:01 +09002617 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2618
2619 initializeCec(INITIATED_BY_ENABLE_CEC);
2620 }
2621
2622 @ServiceThreadOnly
2623 private void disableHdmiControlService() {
2624 disableDevices(new PendingActionClearedCallback() {
2625 @Override
2626 public void onCleared(HdmiCecLocalDevice device) {
2627 assertRunOnServiceThread();
2628 mCecController.flush(new Runnable() {
2629 @Override
2630 public void run() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09002631 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Amy718e41e2018-08-17 17:23:37 -07002632 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
Yuncheol Heof1702482014-11-27 19:52:01 +09002633 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2634 clearLocalDevices();
2635 }
2636 });
2637 }
2638 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002639 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002640
2641 @ServiceThreadOnly
2642 void setActivePortId(int portId) {
2643 assertRunOnServiceThread();
2644 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002645
2646 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2647 // and no further switching is done.
2648 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002649 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002650
Amy123ec402018-09-25 10:56:31 -07002651 ActiveSource getActiveSource() {
2652 synchronized (mLock) {
2653 return mActiveSource;
2654 }
2655 }
2656
2657 void setActiveSource(int logicalAddress, int physicalAddress) {
2658 synchronized (mLock) {
2659 mActiveSource.logicalAddress = logicalAddress;
2660 mActiveSource.physicalAddress = physicalAddress;
2661 }
2662 }
2663
2664 // This method should only be called when the device can be the active source
2665 // and all the device types call into this method.
2666 // For example, when receiving broadcast messages, all the device types will call this
2667 // method but only one of them will be the Active Source.
2668 protected void setAndBroadcastActiveSource(
2669 HdmiCecMessage message, int physicalAddress, int deviceType) {
2670 // If the device has both playback and audio system logical addresses,
2671 // playback will claim active source. Otherwise audio system will.
2672 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
2673 HdmiCecLocalDevicePlayback playback = playback();
2674 playback.setIsActiveSource(true);
2675 playback.wakeUpIfActiveSource();
2676 playback.maySendActiveSource(message.getSource());
2677 setActiveSource(playback.mAddress, physicalAddress);
2678 }
2679
2680 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
2681 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
2682 if (playback() != null) {
2683 audioSystem.setIsActiveSource(false);
2684 } else {
2685 audioSystem.setIsActiveSource(true);
2686 audioSystem.wakeUpIfActiveSource();
2687 audioSystem.maySendActiveSource(message.getSource());
2688 setActiveSource(audioSystem.mAddress, physicalAddress);
2689 }
2690 }
2691 }
2692
2693 // This method should only be called when the device can be the active source
2694 // and only one of the device types calls into this method.
2695 // For example, when receiving One Touch Play, only playback device handles it
2696 // and this method updates Active Source in all the device types sharing the same
2697 // Physical Address.
2698 protected void setAndBroadcastActiveSourceFromOneDeviceType(
2699 int sourceAddress, int physicalAddress) {
2700 // If the device has both playback and audio system logical addresses,
2701 // playback will claim active source. Otherwise audio system will.
2702 HdmiCecLocalDevicePlayback playback = playback();
2703 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
2704 if (playback != null) {
2705 playback.setIsActiveSource(true);
2706 playback.wakeUpIfActiveSource();
2707 playback.maySendActiveSource(sourceAddress);
2708 if (audioSystem != null) {
2709 audioSystem.setIsActiveSource(false);
2710 }
2711 setActiveSource(playback.mAddress, physicalAddress);
2712 } else {
2713 if (audioSystem != null) {
2714 audioSystem.setIsActiveSource(true);
2715 audioSystem.wakeUpIfActiveSource();
2716 audioSystem.maySendActiveSource(sourceAddress);
2717 setActiveSource(audioSystem.mAddress, physicalAddress);
2718 }
2719 }
2720 }
2721
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002722 @ServiceThreadOnly
2723 void setLastInputForMhl(int portId) {
2724 assertRunOnServiceThread();
2725 mLastInputMhl = portId;
2726 }
2727
2728 @ServiceThreadOnly
2729 int getLastInputForMhl() {
2730 assertRunOnServiceThread();
2731 return mLastInputMhl;
2732 }
2733
2734 /**
2735 * Performs input change, routing control for MHL device.
2736 *
2737 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2738 * @param contentOn {@code true} if RAP data is content on; otherwise false
2739 */
2740 @ServiceThreadOnly
2741 void changeInputForMhl(int portId, boolean contentOn) {
2742 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002743 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002744 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002745 if (portId != Constants.INVALID_PORT_ID) {
2746 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2747 @Override
2748 public void onComplete(int result) throws RemoteException {
2749 // Keep the last input to switch back later when RAP[ContentOff] is received.
2750 // This effectively sets the port to invalid one if the switching is for
2751 // RAP[ContentOff].
2752 setLastInputForMhl(lastInput);
2753 }
2754 });
2755 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002756 // MHL device is always directly connected to the port. Update the active port ID to avoid
2757 // unnecessary post-routing control task.
2758 tv().setActivePortId(portId);
2759
2760 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002761 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002762 // may not be the MHL-enabled one. In this case the device info to be passed to
2763 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002764 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002765 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2766 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002767 invokeInputChangeListener(info);
2768 }
2769
2770 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002771 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002772
2773 synchronized (mLock) {
2774 mMhlInputChangeEnabled = enabled;
2775 }
2776 }
2777
2778 boolean isMhlInputChangeEnabled() {
2779 synchronized (mLock) {
2780 return mMhlInputChangeEnabled;
2781 }
2782 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002783
2784 @ServiceThreadOnly
2785 void displayOsd(int messageId) {
2786 assertRunOnServiceThread();
2787 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2788 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2789 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2790 HdmiControlService.PERMISSION);
2791 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002792
2793 @ServiceThreadOnly
2794 void displayOsd(int messageId, int extra) {
2795 assertRunOnServiceThread();
2796 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2797 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002798 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002799 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2800 HdmiControlService.PERMISSION);
2801 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002802}