blob: fe29060326c65203d378ce48264dbe70b87aac87 [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
Amy489454f2019-01-24 19:06:57 -0800150 // Whether System Audio Mode is activated or not.
151 @GuardedBy("mLock")
152 private boolean mSystemAudioActivated = false;
153
Amy02f31152018-08-28 15:05:42 -0700154 private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
155 SystemProperties.getBoolean(
156 Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
157
Jungshik Jangd643f762014-05-22 19:28:09 +0900158 /**
159 * Interface to report send result.
160 */
161 interface SendMessageCallback {
162 /**
163 * Called when {@link HdmiControlService#sendCecCommand} is completed.
164 *
Yuncheol Heoece603b2014-05-23 20:10:19 +0900165 * @param error result of send request.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900166 * <ul>
Donghyun Chobc6e3722016-11-04 05:25:52 +0900167 * <li>{@link SendMessageResult#SUCCESS}
168 * <li>{@link SendMessageResult#NACK}
169 * <li>{@link SendMessageResult#BUSY}
170 * <li>{@link SendMessageResult#FAIL}
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900171 * </ul>
Jungshik Jangd643f762014-05-22 19:28:09 +0900172 */
Jungshik Jangd643f762014-05-22 19:28:09 +0900173 void onSendCompleted(int error);
174 }
175
Jungshik Jang02bb4262014-05-23 16:48:31 +0900176 /**
177 * Interface to get a list of available logical devices.
178 */
179 interface DevicePollingCallback {
180 /**
181 * Called when device polling is finished.
182 *
183 * @param ackedAddress a list of logical addresses of available devices
184 */
185 void onPollingFinished(List<Integer> ackedAddress);
186 }
187
Terry Heo1ca0a432014-08-18 10:30:32 +0900188 private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900189 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900190 @Override
191 public void onReceive(Context context, Intent intent) {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900192 assertRunOnServiceThread();
Amy3288f8a2018-10-23 18:55:34 -0700193 boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
Yuncheol Heo38db6292014-07-01 14:15:14 +0900194 switch (intent.getAction()) {
195 case Intent.ACTION_SCREEN_OFF:
Amy3288f8a2018-10-23 18:55:34 -0700196 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900197 onStandby(STANDBY_SCREEN_OFF);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900198 }
199 break;
200 case Intent.ACTION_SCREEN_ON:
201 if (isPowerStandbyOrTransient()) {
202 onWakeUp();
203 }
204 break;
Terry Heo1ca0a432014-08-18 10:30:32 +0900205 case Intent.ACTION_CONFIGURATION_CHANGED:
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900206 String language = getMenuLanguage();
Terry Heo1ca0a432014-08-18 10:30:32 +0900207 if (!mLanguage.equals(language)) {
208 onLanguageChanged(language);
209 }
210 break;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900211 case Intent.ACTION_SHUTDOWN:
Amy3288f8a2018-10-23 18:55:34 -0700212 if (isPowerOnOrTransient() && !isReboot) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900213 onStandby(STANDBY_SHUTDOWN);
214 }
215 break;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900216 }
217 }
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900218
219 private String getMenuLanguage() {
220 Locale locale = Locale.getDefault();
221 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
222 // Android always returns "zho" for all Chinese variants.
223 // Use "bibliographic" code defined in CEC639-2 for traditional
224 // Chinese used in Taiwan/Hong Kong/Macau.
225 return "chi";
226 } else {
Kyeongkab.Namec0aac92018-10-10 13:47:29 +0900227 String language = locale.getISO3Language();
228
229 // locale.getISO3Language() returns terminology code and need to
230 // send it as bibliographic code instead since the Bibliographic
231 // codes of ISO/FDIS 639-2 shall be used.
232 // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
233 // But, as it depends on the locale, is not handled here.
234 if (mTerminologyToBibliographicMap.containsKey(language)) {
235 language = mTerminologyToBibliographicMap.get(language);
236 }
237
238 return language;
Jinsuk Kim5fe3a6c2014-10-29 07:02:06 +0900239 }
240 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900241 }
242
Jungshik Jang0792d372014-04-23 17:57:26 +0900243 // A thread to handle synchronous IO of CEC and MHL control service.
244 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
245 // and sparse call it shares a thread to handle IO operations.
246 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
247
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900248 // Used to synchronize the access to the service.
249 private final Object mLock = new Object();
250
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900251 // Type of logical devices hosted in the system. Stored in the unmodifiable list.
252 private final List<Integer> mLocalDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900253
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900254 // List of records for hotplug event listener to handle the the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900255 @GuardedBy("mLock")
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900256 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
257 new ArrayList<>();
258
Jungshik Jangf4249322014-08-21 14:17:05 +0900259 // List of records for device event listener to handle the caller killed in action.
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900260 @GuardedBy("mLock")
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +0900261 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
262 new ArrayList<>();
263
Jungshik Jangf4249322014-08-21 14:17:05 +0900264 // List of records for vendor command listener to handle the caller killed in action.
Jinsuk Kim119160a2014-07-07 18:48:10 +0900265 @GuardedBy("mLock")
266 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
267 new ArrayList<>();
268
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900269 @GuardedBy("mLock")
270 private InputChangeListenerRecord mInputChangeListenerRecord;
271
Jungshik Jangb6591b82014-07-23 16:10:23 +0900272 @GuardedBy("mLock")
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900273 private HdmiRecordListenerRecord mRecordListenerRecord;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900274
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900275 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
276 // handling will be disabled and no request will be handled.
277 @GuardedBy("mLock")
278 private boolean mHdmiControlEnabled;
279
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900280 // Set to true while the service is in normal mode. While set to false, no input change is
281 // allowed. Used for situations where input change can confuse users such as channel auto-scan,
282 // system upgrade, etc., a.k.a. "prohibit mode".
283 @GuardedBy("mLock")
284 private boolean mProhibitMode;
285
Jungshik Jangea67c182014-06-19 22:19:20 +0900286 // List of records for system audio mode change to handle the the caller killed in action.
287 private final ArrayList<SystemAudioModeChangeListenerRecord>
288 mSystemAudioModeChangeListenerRecords = new ArrayList<>();
289
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900290 // Handler used to run a task in service thread.
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900291 private final Handler mHandler = new Handler();
292
Jinsuk Kim50084862014-08-07 13:11:40 +0900293 private final SettingsObserver mSettingsObserver;
294
Jungshik Jangf4249322014-08-21 14:17:05 +0900295 private final HdmiControlBroadcastReceiver
296 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
297
Jungshik Jang0792d372014-04-23 17:57:26 +0900298 @Nullable
299 private HdmiCecController mCecController;
300
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900301 // HDMI port information. Stored in the unmodifiable list to keep the static information
302 // from being modified.
Amy6a58a342019-05-16 18:24:24 -0700303 // This variable is null if the current device does not have hdmi input.
304 @GuardedBy("mLock")
305 private List<HdmiPortInfo> mPortInfo = null;
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900306
Jinsuk Kim2b152012014-07-25 08:22:26 +0900307 // Map from path(physical address) to port ID.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900308 private UnmodifiableSparseIntArray mPortIdMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900309
310 // Map from port ID to HdmiPortInfo.
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900311 private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900312
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900313 // Map from port ID to HdmiDeviceInfo.
314 private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
315
Yuncheol Heo75a77e72014-07-09 18:27:53 +0900316 private HdmiCecMessageValidator mMessageValidator;
317
Yuncheol Heo38db6292014-07-01 14:15:14 +0900318 @ServiceThreadOnly
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900319 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +0900320
321 @ServiceThreadOnly
Terry Heo1ca0a432014-08-18 10:30:32 +0900322 private String mLanguage = Locale.getDefault().getISO3Language();
323
324 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900325 private boolean mStandbyMessageReceived = false;
326
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900327 @ServiceThreadOnly
328 private boolean mWakeUpMessageReceived = false;
329
Jungshik Jang867b4e02014-08-12 13:41:30 +0900330 @ServiceThreadOnly
331 private int mActivePortId = Constants.INVALID_PORT_ID;
332
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900333 // Set to true while the input change by MHL is allowed.
334 @GuardedBy("mLock")
335 private boolean mMhlInputChangeEnabled;
336
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900337 // List of records for MHL Vendor command listener to handle the caller killed in action.
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900338 @GuardedBy("mLock")
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +0900339 private final ArrayList<HdmiMhlVendorCommandListenerRecord>
340 mMhlVendorCommandListenerRecords = new ArrayList<>();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900341
342 @GuardedBy("mLock")
343 private List<HdmiDeviceInfo> mMhlDevices;
344
345 @Nullable
Jinsuk Kim78104122014-08-26 19:32:34 +0900346 private HdmiMhlControllerStub mMhlController;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900347
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900348 @Nullable
349 private TvInputManager mTvInputManager;
350
Jinsuk Kime26d8332015-01-09 08:55:41 +0900351 @Nullable
352 private PowerManager mPowerManager;
353
Amy1d0b1372018-05-24 14:36:25 -0700354 @Nullable
355 private Looper mIoLooper;
356
Amyd58d0aa2018-10-18 14:08:57 -0700357 // Thread safe physical address
358 @GuardedBy("mLock")
359 private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
360
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900361 // Last input port before switching to the MHL port. Should switch back to this port
362 // when the mobile device sends the request one touch play with off.
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900363 // Gets invalidated if we go to other port/input.
364 @ServiceThreadOnly
365 private int mLastInputMhl = Constants.INVALID_PORT_ID;
366
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900367 // Set to true if the logical address allocation is completed.
368 private boolean mAddressAllocated = false;
369
370 // Buffer for processing the incoming cec messages while allocating logical addresses.
371 private final class CecMessageBuffer {
372 private List<HdmiCecMessage> mBuffer = new ArrayList<>();
373
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900374 public boolean bufferMessage(HdmiCecMessage message) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900375 switch (message.getOpcode()) {
376 case Constants.MESSAGE_ACTIVE_SOURCE:
377 bufferActiveSource(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900378 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900379 case Constants.MESSAGE_IMAGE_VIEW_ON:
380 case Constants.MESSAGE_TEXT_VIEW_ON:
381 bufferImageOrTextViewOn(message);
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900382 return true;
Jinping Wange55d3d42019-04-16 17:20:51 +0800383 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
384 bufferSystemAudioModeRequest(message);
385 return true;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900386 // Add here if new message that needs to buffer
387 default:
388 // Do not need to buffer messages other than above
Kyeongkab.Nama15539e2018-09-14 13:55:55 +0900389 return false;
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900390 }
391 }
392
393 public void processMessages() {
394 for (final HdmiCecMessage message : mBuffer) {
395 runOnServiceThread(new Runnable() {
396 @Override
397 public void run() {
398 handleCecCommand(message);
399 }
400 });
401 }
402 mBuffer.clear();
403 }
404
405 private void bufferActiveSource(HdmiCecMessage message) {
406 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
407 mBuffer.add(message);
408 }
409 }
410
411 private void bufferImageOrTextViewOn(HdmiCecMessage message) {
412 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
413 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
414 mBuffer.add(message);
415 }
416 }
417
Jinping Wange55d3d42019-04-16 17:20:51 +0800418 private void bufferSystemAudioModeRequest(HdmiCecMessage message) {
419 if (!replaceMessageIfBuffered(message, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST)) {
420 mBuffer.add(message);
421 }
422 }
423
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900424 // Returns true if the message is replaced
425 private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
426 for (int i = 0; i < mBuffer.size(); i++) {
427 HdmiCecMessage bufferedMessage = mBuffer.get(i);
428 if (bufferedMessage.getOpcode() == opcode) {
429 mBuffer.set(i, message);
430 return true;
431 }
432 }
433 return false;
434 }
435 }
436
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900437 private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
438
439 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900440
Jungshik Jang0792d372014-04-23 17:57:26 +0900441 public HdmiControlService(Context context) {
442 super(context);
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900443 mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
Jinsuk Kim50084862014-08-07 13:11:40 +0900444 mSettingsObserver = new SettingsObserver(mHandler);
Jungshik Jang0792d372014-04-23 17:57:26 +0900445 }
446
Amy8027c942018-09-18 10:23:20 -0700447 protected static List<Integer> getIntList(String string) {
Yuncheol Heo7d9acc72014-08-12 15:30:49 +0900448 ArrayList<Integer> list = new ArrayList<>();
449 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
450 splitter.setString(string);
451 for (String item : splitter) {
452 try {
453 list.add(Integer.parseInt(item));
454 } catch (NumberFormatException e) {
455 Slog.w(TAG, "Can't parseInt: " + item);
456 }
457 }
458 return Collections.unmodifiableList(list);
459 }
460
Jungshik Jang0792d372014-04-23 17:57:26 +0900461 @Override
462 public void onStart() {
Amy1d0b1372018-05-24 14:36:25 -0700463 if (mIoLooper == null) {
464 mIoThread.start();
465 mIoLooper = mIoThread.getLooper();
466 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900467 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900468 mProhibitMode = false;
469 mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900470 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900471
Amy1d0b1372018-05-24 14:36:25 -0700472 if (mCecController == null) {
473 mCecController = HdmiCecController.create(this);
474 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900475 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +0900476 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900477 initializeCec(INITIATED_BY_BOOT_UP);
Amy718e41e2018-08-17 17:23:37 -0700478 } else {
479 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Jungshik Janga9f10622014-07-11 15:36:39 +0900480 }
Jinsuk Kima8a5e502014-05-15 16:51:49 +0900481 } else {
Jungshik Jang0792d372014-04-23 17:57:26 +0900482 Slog.i(TAG, "Device does not support HDMI-CEC.");
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900483 return;
Jungshik Jang0792d372014-04-23 17:57:26 +0900484 }
Amy4e7ff1a2018-06-07 16:24:31 -0700485 if (mMhlController == null) {
486 mMhlController = HdmiMhlControllerStub.create(this);
487 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900488 if (!mMhlController.isReady()) {
Jungshik Jang0792d372014-04-23 17:57:26 +0900489 Slog.i(TAG, "Device does not support MHL-control.");
490 }
Jinsuk Kimed086452014-08-18 15:01:53 +0900491 mMhlDevices = Collections.emptyList();
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900492
493 initPortInfo();
Amy1d0b1372018-05-24 14:36:25 -0700494 if (mMessageValidator == null) {
495 mMessageValidator = new HdmiCecMessageValidator(this);
496 }
Jinsuk Kim8692fc62014-05-29 07:39:22 +0900497 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
Yuncheol Heo63a2e062014-05-27 23:06:01 +0900498
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900499 if (mCecController != null) {
Yuncheol Heo0608b932014-10-13 16:39:18 +0900500 // Register broadcast receiver for power state change.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900501 IntentFilter filter = new IntentFilter();
502 filter.addAction(Intent.ACTION_SCREEN_OFF);
503 filter.addAction(Intent.ACTION_SCREEN_ON);
Rob McConnell30595562016-03-11 11:03:00 +0000504 filter.addAction(Intent.ACTION_SHUTDOWN);
Terry Heo1ca0a432014-08-18 10:30:32 +0900505 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
506 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
Yuncheol Heo0608b932014-10-13 16:39:18 +0900507
508 // Register ContentObserver to monitor the settings change.
509 registerContentObserver();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900510 }
Jinsuk Kim5b8cb002015-01-19 07:30:12 +0900511 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900512 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900513
Amy1d0b1372018-05-24 14:36:25 -0700514 @VisibleForTesting
515 void setCecController(HdmiCecController cecController) {
516 mCecController = cecController;
517 }
518
Amy4e7ff1a2018-06-07 16:24:31 -0700519 @VisibleForTesting
520 void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
521 mMhlController = hdmiMhlController;
522 }
523
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900524 @Override
525 public void onBootPhase(int phase) {
526 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
527 mTvInputManager = (TvInputManager) getContext().getSystemService(
528 Context.TV_INPUT_SERVICE);
Jinsuk Kime26d8332015-01-09 08:55:41 +0900529 mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
Jinsuk Kim7fa3a662014-11-07 15:20:24 +0900530 }
531 }
532
533 TvInputManager getTvInputManager() {
534 return mTvInputManager;
535 }
536
537 void registerTvInputCallback(TvInputCallback callback) {
538 if (mTvInputManager == null) return;
539 mTvInputManager.registerCallback(callback, mHandler);
540 }
541
542 void unregisterTvInputCallback(TvInputCallback callback) {
543 if (mTvInputManager == null) return;
544 mTvInputManager.unregisterCallback(callback);
545 }
546
Jinsuk Kime26d8332015-01-09 08:55:41 +0900547 PowerManager getPowerManager() {
548 return mPowerManager;
549 }
550
Yuncheol Heo25c20292014-07-31 17:59:39 +0900551 /**
552 * Called when the initialization of local devices is complete.
553 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900554 private void onInitializeCecComplete(int initiatedBy) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900555 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
556 mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
557 }
558 mWakeUpMessageReceived = false;
559
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900560 if (isTvDeviceEnabled()) {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900561 mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
Yuncheol Heo0608b932014-10-13 16:39:18 +0900562 }
563 int reason = -1;
564 switch (initiatedBy) {
565 case INITIATED_BY_BOOT_UP:
566 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
567 break;
568 case INITIATED_BY_ENABLE_CEC:
569 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
570 break;
571 case INITIATED_BY_SCREEN_ON:
572 case INITIATED_BY_WAKE_UP_MESSAGE:
573 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
574 break;
575 }
576 if (reason != -1) {
577 invokeVendorCommandListenersOnControlStateChanged(true, reason);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900578 }
579 }
580
Jinsuk Kim50084862014-08-07 13:11:40 +0900581 private void registerContentObserver() {
582 ContentResolver resolver = getContext().getContentResolver();
583 String[] settings = new String[] {
584 Global.HDMI_CONTROL_ENABLED,
585 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
586 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900587 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Jinsuk Kim50084862014-08-07 13:11:40 +0900588 Global.MHL_INPUT_SWITCHING_ENABLED,
Amy0c2e29f2018-10-23 12:17:52 -0700589 Global.MHL_POWER_CHARGE_ENABLED,
Amy44d7af22019-02-21 13:27:26 -0800590 Global.HDMI_CEC_SWITCH_ENABLED,
591 Global.DEVICE_NAME
Jinsuk Kim50084862014-08-07 13:11:40 +0900592 };
Jungshik Jang5691b2f2014-08-18 16:50:12 +0900593 for (String s : settings) {
Jinsuk Kim50084862014-08-07 13:11:40 +0900594 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
595 UserHandle.USER_ALL);
596 }
597 }
598
599 private class SettingsObserver extends ContentObserver {
600 public SettingsObserver(Handler handler) {
601 super(handler);
602 }
603
Jungshik Jangf67113f2014-08-22 16:27:19 +0900604 // onChange is set up to run in service thread.
Jinsuk Kim50084862014-08-07 13:11:40 +0900605 @Override
606 public void onChange(boolean selfChange, Uri uri) {
607 String option = uri.getLastPathSegment();
608 boolean enabled = readBooleanSetting(option, true);
609 switch (option) {
610 case Global.HDMI_CONTROL_ENABLED:
611 setControlEnabled(enabled);
612 break;
613 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900614 if (isTvDeviceEnabled()) {
615 tv().setAutoWakeup(enabled);
616 }
Donghyun Chobc6e3722016-11-04 05:25:52 +0900617 setCecOption(OptionKey.WAKEUP, enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900618 break;
619 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900620 for (int type : mLocalDevices) {
621 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
Terry Heodd371ec2015-12-10 15:31:05 +0900622 if (localDevice != null) {
623 localDevice.setAutoDeviceOff(enabled);
624 }
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900625 }
Jinsuk Kim50084862014-08-07 13:11:40 +0900626 // No need to propagate to HAL.
627 break;
Donghyun Choc1fa9af2016-12-27 18:31:09 +0900628 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
629 if (isTvDeviceEnabled()) {
630 tv().setSystemAudioControlFeatureEnabled(enabled);
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900631 }
Amy79db52f2018-10-23 12:45:17 -0700632 if (isAudioSystemDevice()) {
Amy8ba147c2018-11-16 10:59:18 -0800633 if (audioSystem() == null) {
634 Slog.e(TAG, "Audio System device has not registered yet."
635 + " Can't turn system audio mode on.");
636 break;
637 }
Amy79db52f2018-10-23 12:45:17 -0700638 audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
639 }
Donghyun Cho2601f8d2016-03-25 20:18:06 +0900640 break;
Amy0c2e29f2018-10-23 12:17:52 -0700641 case Global.HDMI_CEC_SWITCH_ENABLED:
642 if (isAudioSystemDevice()) {
Amy7f69d672018-11-09 15:46:47 -0800643 if (audioSystem() == null) {
644 Slog.w(TAG, "Switch device has not registered yet."
645 + " Can't turn routing on.");
646 break;
647 }
Amy0c2e29f2018-10-23 12:17:52 -0700648 audioSystem().setRoutingControlFeatureEnables(enabled);
649 }
650 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900651 case Global.MHL_INPUT_SWITCHING_ENABLED:
Yuncheol Heo08a1be82014-08-12 20:58:41 +0900652 setMhlInputChangeEnabled(enabled);
Jinsuk Kim50084862014-08-07 13:11:40 +0900653 break;
654 case Global.MHL_POWER_CHARGE_ENABLED:
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900655 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
Jinsuk Kim50084862014-08-07 13:11:40 +0900656 break;
Amy44d7af22019-02-21 13:27:26 -0800657 case Global.DEVICE_NAME:
658 String deviceName = readStringSetting(option, Build.MODEL);
659 setDisplayName(deviceName);
660 break;
Jinsuk Kim50084862014-08-07 13:11:40 +0900661 }
662 }
663 }
664
665 private static int toInt(boolean enabled) {
666 return enabled ? ENABLED : DISABLED;
667 }
668
Amyaa8ae682018-12-12 16:11:50 -0800669 @VisibleForTesting
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900670 boolean readBooleanSetting(String key, boolean defVal) {
671 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900672 return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900673 }
674
675 void writeBooleanSetting(String key, boolean value) {
676 ContentResolver cr = getContext().getContentResolver();
Jinsuk Kim50084862014-08-07 13:11:40 +0900677 Global.putInt(cr, key, toInt(value));
678 }
679
Amy59c06c12019-01-18 15:35:15 -0800680 void writeStringSystemProperty(String key, String value) {
681 SystemProperties.set(key, value);
682 }
683
684 @VisibleForTesting
685 boolean readBooleanSystemProperty(String key, boolean defVal) {
686 return SystemProperties.getBoolean(key, defVal);
Amyaa8ae682018-12-12 16:11:50 -0800687 }
688
Amy44d7af22019-02-21 13:27:26 -0800689 String readStringSetting(String key, String defVal) {
690 ContentResolver cr = getContext().getContentResolver();
691 String content = Global.getString(cr, key);
692 if (TextUtils.isEmpty(content)) {
693 return defVal;
694 }
695 return content;
696 }
697
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900698 private void initializeCec(int initiatedBy) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900699 mAddressAllocated = false;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900700 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
701 mCecController.setLanguage(mLanguage);
Yuncheol Heob5021862014-09-02 10:36:04 +0900702 initializeLocalDevices(initiatedBy);
Jungshik Janga9f10622014-07-11 15:36:39 +0900703 }
704
Jungshik Janga5b74142014-06-23 18:03:10 +0900705 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900706 private void initializeLocalDevices(final int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900707 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900708 // A container for [Device type, Local device info].
709 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
Yuncheol Heob5021862014-09-02 10:36:04 +0900710 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -0700711 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
712 && isHdmiCecNeverClaimPlaybackLogicAddr) {
713 continue;
714 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900715 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
716 if (localDevice == null) {
717 localDevice = HdmiCecLocalDevice.create(this, type);
718 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900719 localDevice.init();
Yuncheol Heob5021862014-09-02 10:36:04 +0900720 localDevices.add(localDevice);
721 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900722 // It's now safe to flush existing local devices from mCecController since they were
723 // already moved to 'localDevices'.
724 clearLocalDevices();
Yuncheol Heob5021862014-09-02 10:36:04 +0900725 allocateLogicalAddress(localDevices, initiatedBy);
726 }
727
728 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700729 @VisibleForTesting
730 protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
Yuncheol Heob5021862014-09-02 10:36:04 +0900731 final int initiatedBy) {
732 assertRunOnServiceThread();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900733 mCecController.clearLogicalAddress();
Yuncheol Heob5021862014-09-02 10:36:04 +0900734 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
735 final int[] finished = new int[1];
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900736 mAddressAllocated = allocatingDevices.isEmpty();
737
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900738 // For TV device, select request can be invoked while address allocation or device
739 // discovery is in progress. Initialize the request here at the start of allocation,
740 // and process the collected requests later when the allocation and device discovery
741 // is all completed.
742 mSelectRequestBuffer.clear();
743
Yuncheol Heob5021862014-09-02 10:36:04 +0900744 for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
745 mCecController.allocateLogicalAddress(localDevice.getType(),
Jungshik Jang3ee65722014-06-03 16:22:30 +0900746 localDevice.getPreferredAddress(), new AllocateAddressCallback() {
747 @Override
748 public void onAllocated(int deviceType, int logicalAddress) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900749 if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jang3ee65722014-06-03 16:22:30 +0900750 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
751 } else {
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900752 // Set POWER_STATUS_ON to all local devices because they share lifetime
753 // with system.
754 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
755 HdmiControlManager.POWER_STATUS_ON);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900756 localDevice.setDeviceInfo(deviceInfo);
757 mCecController.addLocalDevice(deviceType, localDevice);
758 mCecController.addLogicalAddress(logicalAddress);
Yuncheol Heob5021862014-09-02 10:36:04 +0900759 allocatedDevices.add(localDevice);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900760 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900761
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900762 // Address allocation completed for all devices. Notify each device.
Yuncheol Heob5021862014-09-02 10:36:04 +0900763 if (allocatingDevices.size() == ++finished[0]) {
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900764 mAddressAllocated = true;
Yuncheol Heob5021862014-09-02 10:36:04 +0900765 if (initiatedBy != INITIATED_BY_HOTPLUG) {
766 // In case of the hotplug we don't call onInitializeCecComplete()
767 // since we reallocate the logical address only.
Yuncheol Heo0608b932014-10-13 16:39:18 +0900768 onInitializeCecComplete(initiatedBy);
Yuncheol Heob5021862014-09-02 10:36:04 +0900769 }
770 notifyAddressAllocated(allocatedDevices, initiatedBy);
Jinsuk Kim964c00d2015-01-16 15:20:17 +0900771 mCecMessageBuffer.processMessages();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900772 }
773 }
774 });
775 }
776 }
777
Jungshik Janga5b74142014-06-23 18:03:10 +0900778 @ServiceThreadOnly
Yuncheol Heob5021862014-09-02 10:36:04 +0900779 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900780 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +0900781 for (HdmiCecLocalDevice device : devices) {
782 int address = device.getDeviceInfo().getLogicalAddress();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900783 device.handleAddressAllocated(address, initiatedBy);
Jungshik Jang3ee65722014-06-03 16:22:30 +0900784 }
Jinsuk Kimf98b9e82015-10-05 14:24:48 +0900785 if (isTvDeviceEnabled()) {
786 tv().setSelectRequestBuffer(mSelectRequestBuffer);
787 }
Jungshik Jang3ee65722014-06-03 16:22:30 +0900788 }
789
Donghyun Chofc462b92016-05-13 21:06:02 +0900790 boolean isAddressAllocated() {
791 return mAddressAllocated;
792 }
793
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900794 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
795 // keep them in one place.
Jungshik Janga5b74142014-06-23 18:03:10 +0900796 @ServiceThreadOnly
Amy4e7ff1a2018-06-07 16:24:31 -0700797 @VisibleForTesting
798 protected void initPortInfo() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900799 assertRunOnServiceThread();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900800 HdmiPortInfo[] cecPortInfo = null;
801
Amyd58d0aa2018-10-18 14:08:57 -0700802 synchronized (mLock) {
803 mPhysicalAddress = getPhysicalAddress();
804 }
805
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900806 // CEC HAL provides majority of the info while MHL does only MHL support flag for
807 // each port. Return empty array if CEC HAL didn't provide the info.
808 if (mCecController != null) {
809 cecPortInfo = mCecController.getPortInfos();
810 }
811 if (cecPortInfo == null) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900812 return;
813 }
814
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900815 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
816 SparseIntArray portIdMap = new SparseIntArray();
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900817 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
Jinsuk Kim2b152012014-07-25 08:22:26 +0900818 for (HdmiPortInfo info : cecPortInfo) {
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900819 portIdMap.put(info.getAddress(), info.getId());
820 portInfoMap.put(info.getId(), info);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900821 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900822 }
Jinsuk Kim30c74d92014-08-05 17:30:09 +0900823 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
824 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900825 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900826
Amya00e1182018-10-17 16:45:03 -0700827 if (mMhlController == null) {
828 return;
829 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900830 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
831 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
832 for (HdmiPortInfo info : mhlPortInfo) {
833 if (info.isMhlSupported()) {
834 mhlSupportedPorts.add(info.getId());
835 }
836 }
837
838 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
839 // cec port info if we do not have have port that supports MHL.
840 if (mhlSupportedPorts.isEmpty()) {
Amy6a58a342019-05-16 18:24:24 -0700841 setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
Jinsuk Kimf4eb72d2014-07-25 13:02:51 +0900842 return;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900843 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +0900844 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
845 for (HdmiPortInfo info : cecPortInfo) {
846 if (mhlSupportedPorts.contains(info.getId())) {
847 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
848 info.isCecSupported(), true, info.isArcSupported()));
849 } else {
850 result.add(info);
851 }
852 }
Amy6a58a342019-05-16 18:24:24 -0700853 setPortInfo(Collections.unmodifiableList(result));
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900854 }
855
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900856 List<HdmiPortInfo> getPortInfo() {
Amy6a58a342019-05-16 18:24:24 -0700857 synchronized (mLock) {
858 return mPortInfo;
859 }
860 }
861
862 void setPortInfo(List<HdmiPortInfo> portInfo) {
863 synchronized (mLock) {
864 mPortInfo = portInfo;
865 }
Jungshik Jang2738e2d2014-08-19 09:30:05 +0900866 }
867
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900868 /**
869 * Returns HDMI port information for the given port id.
870 *
871 * @param portId HDMI port id
872 * @return {@link HdmiPortInfo} for the given port
873 */
874 HdmiPortInfo getPortInfo(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900875 return mPortInfoMap.get(portId, null);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900876 }
877
Jungshik Jange9c77c82014-04-24 20:30:09 +0900878 /**
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900879 * Returns the routing path (physical address) of the HDMI port for the given
880 * port id.
881 */
882 int portIdToPath(int portId) {
883 HdmiPortInfo portInfo = getPortInfo(portId);
884 if (portInfo == null) {
885 Slog.e(TAG, "Cannot find the port info: " + portId);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900886 return Constants.INVALID_PHYSICAL_ADDRESS;
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900887 }
888 return portInfo.getAddress();
889 }
890
891 /**
Amya00e1182018-10-17 16:45:03 -0700892 * Returns the id of HDMI port located at the current device that runs this method.
893 *
894 * For TV with physical address 0x0000, target device 0x1120, we want port physical address
895 * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
896 * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
897 *
898 * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
899 *
900 * @param path the target device's physical address.
901 * @return the id of the port that the target device eventually connects to
902 * on the current device.
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900903 */
904 int pathToPortId(int path) {
Amya00e1182018-10-17 16:45:03 -0700905 int mask = 0xF000;
906 int finalMask = 0xF000;
Amyf7782b32018-11-16 11:21:03 -0800907 int physicalAddress;
908 synchronized (mLock) {
909 physicalAddress = mPhysicalAddress;
910 }
Amy3eaa85f2018-10-18 14:17:47 -0700911 int maskedAddress = physicalAddress;
912
913 while (maskedAddress != 0) {
914 maskedAddress = physicalAddress & mask;
Amya00e1182018-10-17 16:45:03 -0700915 finalMask |= mask;
Amy3eaa85f2018-10-18 14:17:47 -0700916 mask >>= 4;
Amya00e1182018-10-17 16:45:03 -0700917 }
Amy3eaa85f2018-10-18 14:17:47 -0700918
Amya00e1182018-10-17 16:45:03 -0700919 int portAddress = path & finalMask;
Jinsuk Kim2b152012014-07-25 08:22:26 +0900920 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900921 }
922
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900923 boolean isValidPortId(int portId) {
Jinsuk Kim2b152012014-07-25 08:22:26 +0900924 return getPortInfo(portId) != null;
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900925 }
926
Jinsuk Kim401e3de2014-06-14 07:47:39 +0900927 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900928 * Returns {@link Looper} for IO operation.
929 *
930 * <p>Declared as package-private.
931 */
Amy1d0b1372018-05-24 14:36:25 -0700932 @Nullable
Jungshik Jange9c77c82014-04-24 20:30:09 +0900933 Looper getIoLooper() {
Amy1d0b1372018-05-24 14:36:25 -0700934 return mIoLooper;
935 }
936
937 @VisibleForTesting
938 void setIoLooper(Looper ioLooper) {
939 mIoLooper = ioLooper;
940 }
941
942 @VisibleForTesting
943 void setMessageValidator(HdmiCecMessageValidator messageValidator) {
944 mMessageValidator = messageValidator;
Jungshik Jange9c77c82014-04-24 20:30:09 +0900945 }
946
947 /**
948 * Returns {@link Looper} of main thread. Use this {@link Looper} instance
949 * for tasks that are running on main service thread.
950 *
951 * <p>Declared as package-private.
952 */
953 Looper getServiceLooper() {
Jungshik Jang67ea5212014-05-15 14:05:24 +0900954 return mHandler.getLooper();
Jungshik Jange9c77c82014-04-24 20:30:09 +0900955 }
Jinsuk Kimc70d2292014-04-30 15:43:16 +0900956
957 /**
Jungshik Jang3ee65722014-06-03 16:22:30 +0900958 * Returns physical address of the device.
959 */
960 int getPhysicalAddress() {
961 return mCecController.getPhysicalAddress();
962 }
963
964 /**
965 * Returns vendor id of CEC service.
966 */
967 int getVendorId() {
968 return mCecController.getVendorId();
969 }
970
Jungshik Janga5b74142014-06-23 18:03:10 +0900971 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900972 HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900973 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +0900974 return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900975 }
976
Jinsuk Kim6ad7cbd2015-01-06 11:30:56 +0900977 @ServiceThreadOnly
978 HdmiDeviceInfo getDeviceInfoByPort(int port) {
979 assertRunOnServiceThread();
980 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
981 if (info != null) {
982 return info.getInfo();
983 }
984 return null;
985 }
986
Jungshik Jang3ee65722014-06-03 16:22:30 +0900987 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900988 * Returns version of CEC.
989 */
990 int getCecVersion() {
991 return mCecController.getVersion();
992 }
993
994 /**
Jungshik Jang60cffce2014-06-12 18:03:04 +0900995 * Whether a device of the specified physical address is connected to ARC enabled port.
996 */
997 boolean isConnectedToArcPort(int physicalAddress) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900998 int portId = pathToPortId(physicalAddress);
Jinsuk Kim2b152012014-07-25 08:22:26 +0900999 if (portId != Constants.INVALID_PORT_ID) {
1000 return mPortInfoMap.get(portId).isArcSupported();
Jungshik Jang60cffce2014-06-12 18:03:04 +09001001 }
1002 return false;
1003 }
1004
Jinsuk Kim7b0cf642015-04-14 09:43:45 +09001005 @ServiceThreadOnly
1006 boolean isConnected(int portId) {
1007 assertRunOnServiceThread();
1008 return mCecController.isConnected(portId);
1009 }
1010
Jungshik Jang79c58a42014-06-16 16:45:36 +09001011 void runOnServiceThread(Runnable runnable) {
Jungshik Jang67ea5212014-05-15 14:05:24 +09001012 mHandler.post(runnable);
1013 }
1014
Yuncheol Heo63a2e062014-05-27 23:06:01 +09001015 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
1016 mHandler.postAtFrontOfQueue(runnable);
1017 }
1018
1019 private void assertRunOnServiceThread() {
1020 if (Looper.myLooper() != mHandler.getLooper()) {
1021 throw new IllegalStateException("Should run on service thread.");
1022 }
1023 }
1024
Jungshik Jang67ea5212014-05-15 14:05:24 +09001025 /**
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001026 * Transmit a CEC command to CEC bus.
1027 *
1028 * @param command CEC command to send out
Jungshik Jangd643f762014-05-22 19:28:09 +09001029 * @param callback interface used to the result of send command
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001030 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001031 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +09001032 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001033 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +09001034 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001035 mCecController.sendCommand(command, callback);
1036 } else {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001037 HdmiLogger.error("Invalid message type:" + command);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001038 if (callback != null) {
Donghyun Chobc6e3722016-11-04 05:25:52 +09001039 callback.onSendCompleted(SendMessageResult.FAIL);
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001040 }
1041 }
Jungshik Jangd643f762014-05-22 19:28:09 +09001042 }
1043
Jungshik Janga5b74142014-06-23 18:03:10 +09001044 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +09001045 void sendCecCommand(HdmiCecMessage command) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001046 assertRunOnServiceThread();
Jungshik Jang5f75cbd2014-08-07 12:02:29 +09001047 sendCecCommand(command, null);
Jinsuk Kimc70d2292014-04-30 15:43:16 +09001048 }
1049
Yuncheol Heo6aae6522014-08-05 14:48:37 +09001050 /**
1051 * Send <Feature Abort> command on the given CEC message if possible.
1052 * If the aborted message is invalid, then it wont send the message.
1053 * @param command original command to be aborted
1054 * @param reason reason of feature abort
1055 */
1056 @ServiceThreadOnly
1057 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
1058 assertRunOnServiceThread();
1059 mCecController.maySendFeatureAbortCommand(command, reason);
1060 }
1061
Jungshik Janga5b74142014-06-23 18:03:10 +09001062 @ServiceThreadOnly
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001063 boolean handleCecCommand(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001064 assertRunOnServiceThread();
Yuncheol Heo4c212892014-09-12 14:32:46 +09001065 int errorCode = mMessageValidator.isValid(message);
1066 if (errorCode != HdmiCecMessageValidator.OK) {
Yuncheol Heoa95f1a92014-11-06 08:25:39 +09001067 // We'll not response on the messages with the invalid source or destination
1068 // or with parameter length shorter than specified in the standard.
Yuncheol Heo4c212892014-09-12 14:32:46 +09001069 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
1070 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
1071 }
1072 return true;
Yuncheol Heo75a77e72014-07-09 18:27:53 +09001073 }
Kyeongkab.Nama15539e2018-09-14 13:55:55 +09001074
1075 if (dispatchMessageToLocalDevice(message)) {
1076 return true;
1077 }
1078
1079 return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
Jungshik Jang092b4452014-06-11 15:19:17 +09001080 }
1081
Donghyun Chobc6e3722016-11-04 05:25:52 +09001082 void enableAudioReturnChannel(int portId, boolean enabled) {
1083 mCecController.enableAudioReturnChannel(portId, enabled);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001084 }
1085
Jungshik Janga5b74142014-06-23 18:03:10 +09001086 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +09001087 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001088 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +09001089 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001090 if (device.dispatchMessage(message)
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001091 && message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +09001092 return true;
1093 }
1094 }
Jungshik Jang60cffce2014-06-12 18:03:04 +09001095
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001096 if (message.getDestination() != Constants.ADDR_BROADCAST) {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001097 HdmiLogger.warning("Unhandled cec command:" + message);
Jungshik Jang3a959fc2014-07-03 09:34:05 +09001098 }
Jungshik Jang092b4452014-06-11 15:19:17 +09001099 return false;
Jungshik Janga1fa91f2014-05-08 20:56:41 +09001100 }
1101
Jungshik Jang67ea5212014-05-15 14:05:24 +09001102 /**
1103 * Called when a new hotplug event is issued.
1104 *
Jinsuk Kimed086452014-08-18 15:01:53 +09001105 * @param portId hdmi port number where hot plug event issued.
Jungshik Jang67ea5212014-05-15 14:05:24 +09001106 * @param connected whether to be plugged in or not
1107 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001108 @ServiceThreadOnly
Jinsuk Kimed086452014-08-18 15:01:53 +09001109 void onHotplug(int portId, boolean connected) {
Jungshik Jang60cffce2014-06-12 18:03:04 +09001110 assertRunOnServiceThread();
Yuncheol Heob5021862014-09-02 10:36:04 +09001111
Amy59176da2018-10-12 16:30:54 -07001112 if (connected && !isTvDevice()
1113 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
1114 if (isSwitchDevice()) {
Amy066db152018-10-04 09:54:51 -07001115 initPortInfo();
1116 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
1117 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001118 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1119 for (int type : mLocalDevices) {
Amy02f31152018-08-28 15:05:42 -07001120 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
1121 && isHdmiCecNeverClaimPlaybackLogicAddr) {
1122 continue;
1123 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001124 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
1125 if (localDevice == null) {
1126 localDevice = HdmiCecLocalDevice.create(this, type);
1127 localDevice.init();
1128 }
1129 localDevices.add(localDevice);
Yuncheol Heob5021862014-09-02 10:36:04 +09001130 }
Yuncheol Heob8d62e72014-09-22 19:53:41 +09001131 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
Yuncheol Heob5021862014-09-02 10:36:04 +09001132 }
Yuncheol Heob5021862014-09-02 10:36:04 +09001133
Jungshik Jang79c58a42014-06-16 16:45:36 +09001134 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kimed086452014-08-18 15:01:53 +09001135 device.onHotplug(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09001136 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001137 announceHotplugEvent(portId, connected);
Jungshik Jang67ea5212014-05-15 14:05:24 +09001138 }
1139
Jungshik Jang02bb4262014-05-23 16:48:31 +09001140 /**
1141 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
1142 * devices.
1143 *
1144 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +09001145 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001146 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +09001147 * @param retryCount the number of retry used to send polling message to remote devices
Jakub Pawlowskib0b3a642018-12-05 10:55:57 +01001148 * @throws IllegalArgumentException if {@code pickStrategy} is invalid value
Jungshik Jang02bb4262014-05-23 16:48:31 +09001149 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001150 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +09001151 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
1152 int retryCount) {
Jungshik Janga5b74142014-06-23 18:03:10 +09001153 assertRunOnServiceThread();
Jungshik Jang1de51422014-07-03 11:14:26 +09001154 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
1155 retryCount);
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001156 }
1157
1158 private int checkPollStrategy(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001159 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001160 if (strategy == 0) {
1161 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
1162 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001163 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +09001164 if (iterationStrategy == 0) {
1165 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
1166 }
1167 return strategy | iterationStrategy;
Jungshik Jang02bb4262014-05-23 16:48:31 +09001168 }
1169
Jungshik Jang60cffce2014-06-12 18:03:04 +09001170 List<HdmiCecLocalDevice> getAllLocalDevices() {
1171 assertRunOnServiceThread();
1172 return mCecController.getLocalDeviceList();
1173 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001174
Jungshik Jang79c58a42014-06-16 16:45:36 +09001175 Object getServiceLock() {
1176 return mLock;
1177 }
1178
1179 void setAudioStatus(boolean mute, int volume) {
Donghyun Cho65618452016-12-23 18:30:37 +09001180 if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) {
1181 return;
1182 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001183 AudioManager audioManager = getAudioManager();
1184 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
1185 if (mute) {
1186 if (!muted) {
1187 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
1188 }
1189 } else {
1190 if (muted) {
1191 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
1192 }
1193 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
1194 // volume change notification back to hdmi control service.
Shuichi.Noguchifbb50bc2017-12-06 11:12:33 +09001195 int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
1196 if (0 <= volume && volume <= 100) {
1197 Slog.i(TAG, "volume: " + volume);
1198 flag |= AudioManager.FLAG_SHOW_UI;
1199 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
1200 }
Jungshik Jangb69aafbf2014-07-11 16:29:06 +09001201 }
Jungshik Jang3ee65722014-06-03 16:22:30 +09001202 }
1203
Jungshik Jangea67c182014-06-19 22:19:20 +09001204 void announceSystemAudioModeChange(boolean enabled) {
Jungshik Jangf4249322014-08-21 14:17:05 +09001205 synchronized (mLock) {
1206 for (SystemAudioModeChangeListenerRecord record :
1207 mSystemAudioModeChangeListenerRecords) {
1208 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1209 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001210 }
1211 }
1212
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001213 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
Amy44d7af22019-02-21 13:27:26 -08001214 String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL);
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001215 return new HdmiDeviceInfo(logicalAddress,
Jinsuk Kim2b152012014-07-25 08:22:26 +09001216 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
Amy0361dd72018-11-07 16:54:25 -08001217 getVendorId(), displayName, powerStatus);
Jungshik Jang3ee65722014-06-03 16:22:30 +09001218 }
1219
Amy44d7af22019-02-21 13:27:26 -08001220 // Set the display name in HdmiDeviceInfo of the current devices to content provided by
1221 // Global.DEVICE_NAME. Only set and broadcast if the new name is different.
1222 private void setDisplayName(String newDisplayName) {
1223 for (HdmiCecLocalDevice device : getAllLocalDevices()) {
1224 HdmiDeviceInfo deviceInfo = device.getDeviceInfo();
1225 if (deviceInfo.getDisplayName().equals(newDisplayName)) {
1226 continue;
1227 }
1228 device.setDeviceInfo(new HdmiDeviceInfo(
1229 deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(),
1230 deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(),
1231 newDisplayName, deviceInfo.getDevicePowerStatus()));
1232 sendCecCommand(HdmiCecMessageBuilder.buildSetOsdNameCommand(
1233 device.mAddress, Constants.ADDR_TV, newDisplayName));
1234 }
1235 }
1236
Jungshik Jang7df52862014-08-11 14:35:27 +09001237 @ServiceThreadOnly
Jungshik Jang7df52862014-08-11 14:35:27 +09001238 void handleMhlHotplugEvent(int portId, boolean connected) {
1239 assertRunOnServiceThread();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001240 // Hotplug event is used to add/remove MHL devices as TV input.
Jungshik Jang7df52862014-08-11 14:35:27 +09001241 if (connected) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001242 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1243 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
Jungshik Jang7df52862014-08-11 14:35:27 +09001244 if (oldDevice != null) {
1245 oldDevice.onDeviceRemoved();
1246 Slog.i(TAG, "Old device of port " + portId + " is removed");
1247 }
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001248 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1249 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001250 } else {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001251 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001252 if (device != null) {
1253 device.onDeviceRemoved();
Jinsuk Kim93eed0c2014-10-14 11:52:22 +09001254 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1255 updateSafeMhlInput();
Jungshik Jang7df52862014-08-11 14:35:27 +09001256 } else {
1257 Slog.w(TAG, "No device to remove:[portId=" + portId);
1258 }
1259 }
Jinsuk Kimed086452014-08-18 15:01:53 +09001260 announceHotplugEvent(portId, connected);
Jungshik Jang7df52862014-08-11 14:35:27 +09001261 }
1262
1263 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001264 void handleMhlBusModeChanged(int portId, int busmode) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001265 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001266 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001267 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001268 device.setBusMode(busmode);
Jungshik Jang7df52862014-08-11 14:35:27 +09001269 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001270 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1271 ", busmode:" + busmode + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001272 }
1273 }
1274
1275 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001276 void handleMhlBusOvercurrent(int portId, boolean on) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001277 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001278 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001279 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001280 device.onBusOvercurrentDetected(on);
Jungshik Jang7df52862014-08-11 14:35:27 +09001281 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001282 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
Jungshik Jang7df52862014-08-11 14:35:27 +09001283 }
1284 }
1285
1286 @ServiceThreadOnly
Jinsuk Kima94417a2014-09-12 15:02:07 +09001287 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
Jungshik Jang7df52862014-08-11 14:35:27 +09001288 assertRunOnServiceThread();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001289 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimed086452014-08-18 15:01:53 +09001290
Jungshik Jang7df52862014-08-11 14:35:27 +09001291 if (device != null) {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001292 device.setDeviceStatusChange(adopterId, deviceId);
Jungshik Jang7df52862014-08-11 14:35:27 +09001293 } else {
Jinsuk Kima94417a2014-09-12 15:02:07 +09001294 Slog.w(TAG, "No mhl device exists for device status event[portId:"
Jungshik Jang7df52862014-08-11 14:35:27 +09001295 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1296 }
1297 }
1298
Jinsuk Kimed086452014-08-18 15:01:53 +09001299 @ServiceThreadOnly
1300 private void updateSafeMhlInput() {
1301 assertRunOnServiceThread();
1302 List<HdmiDeviceInfo> inputs = Collections.emptyList();
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001303 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
Jinsuk Kimed086452014-08-18 15:01:53 +09001304 for (int i = 0; i < devices.size(); ++i) {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001305 HdmiMhlLocalDeviceStub device = devices.valueAt(i);
Jinsuk Kimed086452014-08-18 15:01:53 +09001306 HdmiDeviceInfo info = device.getInfo();
1307 if (info != null) {
1308 if (inputs.isEmpty()) {
1309 inputs = new ArrayList<>();
1310 }
1311 inputs.add(device.getInfo());
1312 }
1313 }
1314 synchronized (mLock) {
1315 mMhlDevices = inputs;
1316 }
1317 }
1318
Andreas Gampea36dc622018-02-05 17:19:22 -08001319 @GuardedBy("mLock")
Jinsuk Kimed086452014-08-18 15:01:53 +09001320 private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1321 return mMhlDevices;
1322 }
1323
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001324 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1325 private final IHdmiMhlVendorCommandListener mListener;
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001326
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001327 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001328 mListener = listener;
1329 }
1330
1331 @Override
1332 public void binderDied() {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001333 mMhlVendorCommandListenerRecords.remove(this);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001334 }
1335 }
1336
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001337 // Record class that monitors the event of the caller of being killed. Used to clean up
1338 // the listener list and record list accordingly.
1339 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1340 private final IHdmiHotplugEventListener mListener;
1341
1342 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1343 mListener = listener;
1344 }
1345
1346 @Override
1347 public void binderDied() {
1348 synchronized (mLock) {
1349 mHotplugEventListenerRecords.remove(this);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001350 }
1351 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09001352
1353 @Override
1354 public boolean equals(Object obj) {
1355 if (!(obj instanceof HotplugEventListenerRecord)) return false;
1356 if (obj == this) return true;
1357 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1358 return other.mListener == this.mListener;
1359 }
1360
1361 @Override
1362 public int hashCode() {
1363 return mListener.hashCode();
1364 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001365 }
1366
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001367 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1368 private final IHdmiDeviceEventListener mListener;
1369
1370 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1371 mListener = listener;
1372 }
1373
1374 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001375 public void binderDied() {
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001376 synchronized (mLock) {
1377 mDeviceEventListenerRecords.remove(this);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001378 }
1379 }
1380 }
1381
Jungshik Jangea67c182014-06-19 22:19:20 +09001382 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001383 private final IHdmiSystemAudioModeChangeListener mListener;
Jungshik Jangea67c182014-06-19 22:19:20 +09001384
1385 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1386 mListener = listener;
1387 }
1388
1389 @Override
1390 public void binderDied() {
1391 synchronized (mLock) {
1392 mSystemAudioModeChangeListenerRecords.remove(this);
Jungshik Jangea67c182014-06-19 22:19:20 +09001393 }
1394 }
1395 }
1396
Jinsuk Kim119160a2014-07-07 18:48:10 +09001397 class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1398 private final IHdmiVendorCommandListener mListener;
1399 private final int mDeviceType;
1400
1401 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1402 mListener = listener;
1403 mDeviceType = deviceType;
1404 }
1405
1406 @Override
1407 public void binderDied() {
1408 synchronized (mLock) {
1409 mVendorCommandListenerRecords.remove(this);
1410 }
1411 }
1412 }
1413
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001414 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09001415 private final IHdmiRecordListener mListener;
1416
1417 public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1418 mListener = listener;
1419 }
1420
Jungshik Jangb6591b82014-07-23 16:10:23 +09001421 @Override
1422 public void binderDied() {
1423 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09001424 if (mRecordListenerRecord == this) {
1425 mRecordListenerRecord = null;
1426 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001427 }
1428 }
1429 }
1430
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001431 private void enforceAccessPermission() {
1432 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1433 }
1434
1435 private final class BinderService extends IHdmiControlService.Stub {
1436 @Override
1437 public int[] getSupportedTypes() {
1438 enforceAccessPermission();
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001439 // mLocalDevices is an unmodifiable list - no lock necesary.
1440 int[] localDevices = new int[mLocalDevices.size()];
1441 for (int i = 0; i < localDevices.length; ++i) {
1442 localDevices[i] = mLocalDevices.get(i);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001443 }
Jinsuk Kim0340bbc2014-06-05 11:07:47 +09001444 return localDevices;
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001445 }
1446
1447 @Override
Amy4d3551a2019-01-23 14:17:50 -08001448 @Nullable
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001449 public HdmiDeviceInfo getActiveSource() {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001450 enforceAccessPermission();
Jinsuk Kim7e742062014-07-30 13:19:13 +09001451 HdmiCecLocalDeviceTv tv = tv();
1452 if (tv == null) {
Amy4d3551a2019-01-23 14:17:50 -08001453 if (isTvDevice()) {
1454 Slog.e(TAG, "Local tv device not available.");
1455 return null;
1456 }
Amy0361dd72018-11-07 16:54:25 -08001457 if (isPlaybackDevice()) {
1458 // if playback device itself is the active source,
1459 // return its own device info.
1460 if (playback() != null && playback().mIsActiveSource) {
1461 return playback().getDeviceInfo();
1462 }
1463 // Otherwise get the active source and look for it from the device list
1464 ActiveSource activeSource = mActiveSource;
1465 // If the active source is not set yet, return null
1466 if (!activeSource.isValid()) {
1467 return null;
1468 }
1469 if (audioSystem() != null) {
1470 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1471 for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
1472 if (info.getLogicalAddress() == activeSource.logicalAddress) {
1473 return info;
1474 }
1475 }
1476 }
1477 // If the device info is not in the list yet, return a device info with minimum
1478 // information from mActiveSource.
1479 return new HdmiDeviceInfo(activeSource.logicalAddress,
1480 activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress),
1481 HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0,
1482 HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress));
1483 }
Jinsuk Kim7e742062014-07-30 13:19:13 +09001484 return null;
1485 }
1486 ActiveSource activeSource = tv.getActiveSource();
1487 if (activeSource.isValid()) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001488 return new HdmiDeviceInfo(activeSource.logicalAddress,
1489 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1490 HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
Jinsuk Kim7e742062014-07-30 13:19:13 +09001491 }
1492 int activePath = tv.getActivePath();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001493 if (activePath != HdmiDeviceInfo.PATH_INVALID) {
Jinsuk Kim7640d982015-01-28 16:44:07 +09001494 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
Jinsuk Kimd47abef2015-01-17 07:38:24 +09001495 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
Jinsuk Kim7e742062014-07-30 13:19:13 +09001496 }
1497 return null;
1498 }
1499
1500 @Override
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001501 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001502 enforceAccessPermission();
1503 runOnServiceThread(new Runnable() {
1504 @Override
1505 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001506 if (callback == null) {
1507 Slog.e(TAG, "Callback cannot be null");
1508 return;
1509 }
Amya9d5ca22019-06-06 18:14:28 -07001510 if (isPowerStandby()) {
1511 Slog.e(TAG, "Device is in standby. Not handling deviceSelect");
1512 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1513 return;
1514 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001515 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001516 if (tv == null) {
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09001517 if (!mAddressAllocated) {
1518 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1519 HdmiControlService.this, deviceId, callback));
1520 return;
1521 }
Amy4d3551a2019-01-23 14:17:50 -08001522 if (isTvDevice()) {
1523 Slog.e(TAG, "Local tv device not available");
1524 return;
1525 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001526 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001527 return;
1528 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001529 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001530 if (device != null) {
1531 if (device.getPortId() == tv.getActivePortId()) {
1532 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001533 return;
1534 }
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001535 // Upon selecting MHL device, we send RAP[Content On] to wake up
1536 // the connected mobile device, start routing control to switch ports.
1537 // callback is handled by MHL action.
1538 device.turnOn(callback);
Yuncheol Heo7c5d31e2014-09-03 16:28:54 +09001539 tv.doManualPortSwitching(device.getPortId(), null);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001540 return;
Jinsuk Kim87f22a22014-08-20 10:40:12 +09001541 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001542 tv.deviceSelect(deviceId, callback);
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001543 }
1544 });
1545 }
1546
Jinsuk Kima6ce7702014-05-11 06:54:49 +09001547 @Override
Jinsuk Kima062a932014-06-18 10:00:39 +09001548 public void portSelect(final int portId, final IHdmiControlCallback callback) {
1549 enforceAccessPermission();
1550 runOnServiceThread(new Runnable() {
1551 @Override
1552 public void run() {
Jinsuk Kim72b7d732014-07-24 09:15:35 +09001553 if (callback == null) {
1554 Slog.e(TAG, "Callback cannot be null");
1555 return;
1556 }
Amya9d5ca22019-06-06 18:14:28 -07001557 if (isPowerStandby()) {
1558 Slog.e(TAG, "Device is in standby. Not handling portSelect");
1559 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1560 return;
1561 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001562 HdmiCecLocalDeviceTv tv = tv();
shubangd932cb52018-09-14 17:55:16 -07001563 if (tv != null) {
1564 tv.doManualPortSwitching(portId, callback);
Jinsuk Kima062a932014-06-18 10:00:39 +09001565 return;
1566 }
shubangd932cb52018-09-14 17:55:16 -07001567 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1568 if (audioSystem != null) {
1569 audioSystem.doManualPortSwitching(portId, callback);
1570 return;
1571 }
1572
1573 if (!mAddressAllocated) {
1574 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1575 HdmiControlService.this, portId, callback));
1576 return;
1577 }
1578 Slog.w(TAG, "Local device not available");
1579 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1580 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001581 }
1582 });
1583 }
1584
1585 @Override
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001586 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +09001587 enforceAccessPermission();
1588 runOnServiceThread(new Runnable() {
1589 @Override
1590 public void run() {
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09001591 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09001592 if (device != null) {
1593 device.sendKeyEvent(keyCode, isPressed);
1594 return;
Jinsuk Kima062a932014-06-18 10:00:39 +09001595 }
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001596 if (mCecController != null) {
1597 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1598 if (localDevice == null) {
Amybd8b4fa2019-01-30 11:27:49 -08001599 Slog.w(TAG, "Local device not available to send key event.");
Jungshik Jang4612a6e2014-08-12 22:01:23 +09001600 return;
1601 }
1602 localDevice.sendKeyEvent(keyCode, isPressed);
1603 }
Jinsuk Kima062a932014-06-18 10:00:39 +09001604 }
1605 });
1606 }
1607
1608 @Override
Amybd8b4fa2019-01-30 11:27:49 -08001609 public void sendVolumeKeyEvent(
1610 final int deviceType, final int keyCode, final boolean isPressed) {
1611 enforceAccessPermission();
1612 runOnServiceThread(new Runnable() {
1613 @Override
1614 public void run() {
1615 if (mCecController == null) {
1616 Slog.w(TAG, "CEC controller not available to send volume key event.");
1617 return;
1618 }
1619 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1620 if (localDevice == null) {
1621 Slog.w(TAG, "Local device " + deviceType
1622 + " not available to send volume key event.");
1623 return;
1624 }
1625 localDevice.sendVolumeKeyEvent(keyCode, isPressed);
1626 }
1627 });
1628 }
1629
1630 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001631 public void oneTouchPlay(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001632 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001633 runOnServiceThread(new Runnable() {
1634 @Override
1635 public void run() {
1636 HdmiControlService.this.oneTouchPlay(callback);
1637 }
1638 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001639 }
1640
1641 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001642 public void queryDisplayStatus(final IHdmiControlCallback callback) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001643 enforceAccessPermission();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001644 runOnServiceThread(new Runnable() {
1645 @Override
1646 public void run() {
1647 HdmiControlService.this.queryDisplayStatus(callback);
1648 }
1649 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001650 }
1651
1652 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001653 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001654 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001655 HdmiControlService.this.addHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001656 }
1657
1658 @Override
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09001659 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001660 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001661 HdmiControlService.this.removeHotplugEventListener(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09001662 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001663
1664 @Override
1665 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1666 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001667 HdmiControlService.this.addDeviceEventListener(listener);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001668 }
1669
1670 @Override
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001671 public List<HdmiPortInfo> getPortInfo() {
1672 enforceAccessPermission();
Amy6a58a342019-05-16 18:24:24 -07001673 return HdmiControlService.this.getPortInfo() == null
1674 ? Collections.<HdmiPortInfo>emptyList()
1675 : HdmiControlService.this.getPortInfo();
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09001676 }
Jungshik Jangea67c182014-06-19 22:19:20 +09001677
1678 @Override
1679 public boolean canChangeSystemAudioMode() {
1680 enforceAccessPermission();
1681 HdmiCecLocalDeviceTv tv = tv();
1682 if (tv == null) {
1683 return false;
1684 }
Jungshik Jange9cf1582014-06-23 17:28:58 +09001685 return tv.hasSystemAudioDevice();
Jungshik Jangea67c182014-06-19 22:19:20 +09001686 }
1687
1688 @Override
1689 public boolean getSystemAudioMode() {
Shubang Lu00b976a2018-08-01 18:11:46 -07001690 // TODO(shubang): handle getSystemAudioMode() for all device types
Jungshik Jangea67c182014-06-19 22:19:20 +09001691 enforceAccessPermission();
1692 HdmiCecLocalDeviceTv tv = tv();
Shubang Lu00b976a2018-08-01 18:11:46 -07001693 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1694 return (tv != null && tv.isSystemAudioActivated())
1695 || (audioSystem != null && audioSystem.isSystemAudioActivated());
Jungshik Jangea67c182014-06-19 22:19:20 +09001696 }
1697
1698 @Override
Amyd58d0aa2018-10-18 14:08:57 -07001699 public int getPhysicalAddress() {
1700 enforceAccessPermission();
1701 synchronized (mLock) {
1702 return mPhysicalAddress;
1703 }
1704 }
1705
1706 @Override
Jungshik Jangea67c182014-06-19 22:19:20 +09001707 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1708 enforceAccessPermission();
1709 runOnServiceThread(new Runnable() {
1710 @Override
1711 public void run() {
1712 HdmiCecLocalDeviceTv tv = tv();
1713 if (tv == null) {
1714 Slog.w(TAG, "Local tv device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001715 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +09001716 return;
1717 }
1718 tv.changeSystemAudioMode(enabled, callback);
1719 }
1720 });
1721 }
1722
1723 @Override
1724 public void addSystemAudioModeChangeListener(
1725 final IHdmiSystemAudioModeChangeListener listener) {
1726 enforceAccessPermission();
1727 HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1728 }
1729
1730 @Override
1731 public void removeSystemAudioModeChangeListener(
1732 final IHdmiSystemAudioModeChangeListener listener) {
1733 enforceAccessPermission();
1734 HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1735 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001736
1737 @Override
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001738 public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1739 enforceAccessPermission();
1740 HdmiControlService.this.setInputChangeListener(listener);
1741 }
1742
1743 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001744 public List<HdmiDeviceInfo> getInputDevices() {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001745 enforceAccessPermission();
1746 // No need to hold the lock for obtaining TV device as the local device instance
1747 // is preserved while the HDMI control is enabled.
1748 HdmiCecLocalDeviceTv tv = tv();
Jinsuk Kimed086452014-08-18 15:01:53 +09001749 synchronized (mLock) {
1750 List<HdmiDeviceInfo> cecDevices = (tv == null)
1751 ? Collections.<HdmiDeviceInfo>emptyList()
1752 : tv.getSafeExternalInputsLocked();
1753 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001754 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001755 }
1756
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001757 // Returns all the CEC devices on the bus including system audio, switch,
1758 // even those of reserved type.
1759 @Override
1760 public List<HdmiDeviceInfo> getDeviceList() {
1761 enforceAccessPermission();
1762 HdmiCecLocalDeviceTv tv = tv();
Amy6f031af2018-10-30 16:38:33 -07001763 if (tv != null) {
1764 synchronized (mLock) {
1765 return tv.getSafeCecDevicesLocked();
1766 }
1767 } else {
1768 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1769 synchronized (mLock) {
1770 return (audioSystem == null)
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001771 ? Collections.<HdmiDeviceInfo>emptyList()
Amy6f031af2018-10-30 16:38:33 -07001772 : audioSystem.getSafeCecDevicesLocked();
1773 }
Jinsuk Kimbdf27fb2014-10-20 10:00:04 +09001774 }
1775 }
1776
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001777 @Override
Amy6f031af2018-10-30 16:38:33 -07001778 public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
1779 enforceAccessPermission();
1780 runOnServiceThread(new Runnable() {
1781 @Override
1782 public void run() {
Amy4879d5c2018-11-13 16:06:15 -08001783 Slog.w(TAG, "Device "
1784 + logicalAddress + " power status is " + powerStatus
1785 + " before standby command sent out");
1786 sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1787 getRemoteControlSourceAddress(), logicalAddress));
Amy6f031af2018-10-30 16:38:33 -07001788 }
1789 });
1790 }
1791
1792 @Override
1793 public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
1794 // TODO(amyjojo): implement the method
1795 }
1796
1797 @Override
Amyd5a15142019-03-22 11:22:05 -07001798 // TODO(b/128427908): add a result callback
Amy6f031af2018-10-30 16:38:33 -07001799 public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
1800 enforceAccessPermission();
1801 runOnServiceThread(new Runnable() {
1802 @Override
1803 public void run() {
1804 HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
1805 getRemoteControlSourceAddress(), physicalAddress);
1806 if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
1807 if (getSwitchDevice() != null) {
1808 getSwitchDevice().handleSetStreamPath(setStreamPath);
1809 } else {
1810 Slog.e(TAG, "Can't get the correct local device to handle routing.");
1811 }
Amy6f031af2018-10-30 16:38:33 -07001812 }
Amycdd25e92018-11-13 12:22:16 -08001813 sendCecCommand(setStreamPath);
Amy6f031af2018-10-30 16:38:33 -07001814 }
1815 });
1816 }
1817
1818 @Override
Jungshik Jang41d97462014-06-30 22:26:29 +09001819 public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1820 final int maxIndex) {
1821 enforceAccessPermission();
1822 runOnServiceThread(new Runnable() {
1823 @Override
1824 public void run() {
1825 HdmiCecLocalDeviceTv tv = tv();
1826 if (tv == null) {
1827 Slog.w(TAG, "Local tv device not available");
1828 return;
1829 }
1830 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1831 }
1832 });
1833 }
1834
1835 @Override
1836 public void setSystemAudioMute(final boolean mute) {
1837 enforceAccessPermission();
1838 runOnServiceThread(new Runnable() {
1839 @Override
1840 public void run() {
1841 HdmiCecLocalDeviceTv tv = tv();
1842 if (tv == null) {
1843 Slog.w(TAG, "Local tv device not available");
1844 return;
1845 }
1846 tv.changeMute(mute);
1847 }
1848 });
1849 }
1850
1851 @Override
Jungshik Janga13da0d2014-06-30 16:26:06 +09001852 public void setArcMode(final boolean enabled) {
1853 enforceAccessPermission();
1854 runOnServiceThread(new Runnable() {
1855 @Override
1856 public void run() {
1857 HdmiCecLocalDeviceTv tv = tv();
1858 if (tv == null) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001859 Slog.w(TAG, "Local tv device not available to change arc mode.");
Jungshik Janga13da0d2014-06-30 16:26:06 +09001860 return;
1861 }
1862 }
1863 });
1864 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001865
1866 @Override
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001867 public void setProhibitMode(final boolean enabled) {
1868 enforceAccessPermission();
1869 if (!isTvDevice()) {
1870 return;
1871 }
1872 HdmiControlService.this.setProhibitMode(enabled);
1873 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09001874
1875 @Override
1876 public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1877 final int deviceType) {
1878 enforceAccessPermission();
Jungshik Jangf4249322014-08-21 14:17:05 +09001879 HdmiControlService.this.addVendorCommandListener(listener, deviceType);
Jinsuk Kim119160a2014-07-07 18:48:10 +09001880 }
1881
1882 @Override
1883 public void sendVendorCommand(final int deviceType, final int targetAddress,
1884 final byte[] params, final boolean hasVendorId) {
1885 enforceAccessPermission();
1886 runOnServiceThread(new Runnable() {
1887 @Override
1888 public void run() {
1889 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1890 if (device == null) {
1891 Slog.w(TAG, "Local device not available");
1892 return;
1893 }
1894 if (hasVendorId) {
1895 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1896 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1897 getVendorId(), params));
1898 } else {
1899 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1900 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1901 }
1902 }
1903 });
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001904 }
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001905
1906 @Override
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001907 public void sendStandby(final int deviceType, final int deviceId) {
1908 enforceAccessPermission();
1909 runOnServiceThread(new Runnable() {
1910 @Override
1911 public void run() {
Jinsuk Kim61c94d12015-01-15 07:00:28 +09001912 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1913 if (mhlDevice != null) {
1914 mhlDevice.sendStandby();
1915 return;
1916 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001917 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1918 if (device == null) {
Amy777abd72018-09-10 16:11:33 -07001919 device = audioSystem();
1920 }
1921 if (device == null) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09001922 Slog.w(TAG, "Local device not available");
1923 return;
1924 }
1925 device.sendStandby(deviceId);
1926 }
1927 });
1928 }
1929
1930 @Override
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001931 public void setHdmiRecordListener(IHdmiRecordListener listener) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001932 enforceAccessPermission();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001933 HdmiControlService.this.setHdmiRecordListener(listener);
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001934 }
1935
1936 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001937 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001938 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001939 runOnServiceThread(new Runnable() {
1940 @Override
1941 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001942 if (!isTvDeviceEnabled()) {
1943 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001944 return;
1945 }
1946 tv().startOneTouchRecord(recorderAddress, recordSource);
1947 }
1948 });
Jungshik Jangbffb0632014-07-22 16:56:52 +09001949 }
1950
1951 @Override
Jungshik Jangb6591b82014-07-23 16:10:23 +09001952 public void stopOneTouchRecord(final int recorderAddress) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001953 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001954 runOnServiceThread(new Runnable() {
1955 @Override
1956 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001957 if (!isTvDeviceEnabled()) {
1958 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001959 return;
1960 }
1961 tv().stopOneTouchRecord(recorderAddress);
1962 }
1963 });
1964 }
1965
1966 @Override
1967 public void startTimerRecording(final int recorderAddress, final int sourceType,
1968 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001969 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001970 runOnServiceThread(new Runnable() {
1971 @Override
1972 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001973 if (!isTvDeviceEnabled()) {
1974 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001975 return;
1976 }
1977 tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1978 }
1979 });
1980 }
1981
1982 @Override
1983 public void clearTimerRecording(final int recorderAddress, final int sourceType,
1984 final byte[] recordSource) {
Jinsuk Kimb22d9ee2014-10-21 07:14:46 +09001985 enforceAccessPermission();
Jungshik Jangb6591b82014-07-23 16:10:23 +09001986 runOnServiceThread(new Runnable() {
1987 @Override
1988 public void run() {
Jinsuk Kimde7a4242014-12-05 12:05:27 +09001989 if (!isTvDeviceEnabled()) {
1990 Slog.w(TAG, "TV device is not enabled.");
Jungshik Jangb6591b82014-07-23 16:10:23 +09001991 return;
1992 }
1993 tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1994 }
1995 });
Jungshik Janga6b2a7a2014-07-16 18:04:49 +09001996 }
Jungshik Jangf4249322014-08-21 14:17:05 +09001997
1998 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09001999 public void sendMhlVendorCommand(final int portId, final int offset, final int length,
Jungshik Jangf4249322014-08-21 14:17:05 +09002000 final byte[] data) {
2001 enforceAccessPermission();
2002 runOnServiceThread(new Runnable() {
2003 @Override
2004 public void run() {
Jungshik Jangf4249322014-08-21 14:17:05 +09002005 if (!isControlEnabled()) {
2006 Slog.w(TAG, "Hdmi control is disabled.");
2007 return ;
2008 }
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002009 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jungshik Jangf4249322014-08-21 14:17:05 +09002010 if (device == null) {
2011 Slog.w(TAG, "Invalid port id:" + portId);
2012 return;
2013 }
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002014 mMhlController.sendVendorCommand(portId, offset, length, data);
Jungshik Jangf4249322014-08-21 14:17:05 +09002015 }
2016 });
2017 }
2018
2019 @Override
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002020 public void addHdmiMhlVendorCommandListener(
2021 IHdmiMhlVendorCommandListener listener) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002022 enforceAccessPermission();
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002023 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002024 }
Terry Heo959d2db2014-08-28 16:45:41 +09002025
2026 @Override
Donghyun Chob3515642017-03-02 13:47:40 +09002027 public void setStandbyMode(final boolean isStandbyModeOn) {
2028 enforceAccessPermission();
2029 runOnServiceThread(new Runnable() {
2030 @Override
2031 public void run() {
2032 HdmiControlService.this.setStandbyMode(isStandbyModeOn);
2033 }
2034 });
2035 }
2036
2037 @Override
Shubangc480a712018-06-11 18:02:42 -07002038 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
2039 final boolean isMute) {
2040 enforceAccessPermission();
2041 runOnServiceThread(new Runnable() {
2042 @Override
2043 public void run() {
2044 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2045 if (device == null) {
2046 Slog.w(TAG, "Local device not available");
2047 return;
2048 }
2049 if (audioSystem() == null) {
2050 Slog.w(TAG, "audio system is not available");
2051 return;
2052 }
Shubang17b29342018-07-19 17:58:48 -07002053 if (!audioSystem().isSystemAudioActivated()) {
Shubangc480a712018-06-11 18:02:42 -07002054 Slog.w(TAG, "audio system is not in system audio mode");
2055 return;
2056 }
Nick Chalko01b979c2018-10-19 14:54:30 -07002057 audioSystem().reportAudioStatus(Constants.ADDR_TV);
Shubangc480a712018-06-11 18:02:42 -07002058 }
2059 });
2060 }
2061
2062 @Override
Amy4ad4e782018-10-17 17:48:49 -07002063 public void setSystemAudioModeOnForAudioOnlySource() {
2064 enforceAccessPermission();
2065 runOnServiceThread(new Runnable() {
2066 @Override
2067 public void run() {
2068 if (!isAudioSystemDevice()) {
2069 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
2070 return;
2071 }
Amy7e3b6d92018-11-21 11:52:07 -08002072 if (audioSystem() == null) {
2073 Slog.e(TAG, "Audio System local device is not registered");
2074 return;
2075 }
Amy4ad4e782018-10-17 17:48:49 -07002076 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
2077 Slog.e(TAG, "System Audio Mode is not supported.");
2078 return;
2079 }
2080 sendCecCommand(
2081 HdmiCecMessageBuilder.buildSetSystemAudioMode(
2082 audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
2083 }
2084 });
2085 }
2086
2087 @Override
Terry Heo959d2db2014-08-28 16:45:41 +09002088 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -06002089 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
Terry Heo959d2db2014-08-28 16:45:41 +09002090 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
2091
Terry Heo959d2db2014-08-28 16:45:41 +09002092 pw.println("mProhibitMode: " + mProhibitMode);
Nick Chalkob9e48e22018-10-23 06:59:39 -07002093 pw.println("mPowerStatus: " + mPowerStatus);
2094
2095 // System settings
2096 pw.println("System_settings:");
2097 pw.increaseIndent();
2098 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
2099 pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
Amy489454f2019-01-24 19:06:57 -08002100 pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
Nick Chalkob9e48e22018-10-23 06:59:39 -07002101 pw.decreaseIndent();
Jinsuk Kim61c94d12015-01-15 07:00:28 +09002102
2103 pw.println("mMhlController: ");
2104 pw.increaseIndent();
2105 mMhlController.dump(pw);
2106 pw.decreaseIndent();
2107
Nick Chalkob9e48e22018-10-23 06:59:39 -07002108 HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
2109 if (mCecController != null) {
2110 pw.println("mCecController: ");
2111 pw.increaseIndent();
2112 mCecController.dump(pw);
2113 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +09002114 }
Terry Heo959d2db2014-08-28 16:45:41 +09002115 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002116 }
2117
Amy6f031af2018-10-30 16:38:33 -07002118 // Get the source address to send out commands to devices connected to the current device
2119 // when other services interact with HdmiControlService.
2120 private int getRemoteControlSourceAddress() {
2121 if (isAudioSystemDevice()) {
2122 return audioSystem().getDeviceInfo().getLogicalAddress();
2123 } else if (isPlaybackDevice()) {
2124 return playback().getDeviceInfo().getLogicalAddress();
2125 }
2126 return ADDR_UNREGISTERED;
2127 }
2128
2129 // Get the switch device to do CEC routing control
2130 @Nullable
2131 private HdmiCecLocalDeviceSource getSwitchDevice() {
2132 if (isAudioSystemDevice()) {
2133 return audioSystem();
2134 }
2135 if (isPlaybackDevice()) {
2136 return playback();
2137 }
2138 return null;
2139 }
2140
Jungshik Janga5b74142014-06-23 18:03:10 +09002141 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002142 private void oneTouchPlay(final IHdmiControlCallback callback) {
2143 assertRunOnServiceThread();
Amy848a9f22018-08-27 17:21:26 -07002144 HdmiCecLocalDeviceSource source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002145 if (source == null) {
Amy848a9f22018-08-27 17:21:26 -07002146 source = audioSystem();
2147 }
2148
2149 if (source == null) {
2150 Slog.w(TAG, "Local source device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002151 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002152 return;
2153 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002154 source.oneTouchPlay(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002155 }
2156
Jungshik Janga5b74142014-06-23 18:03:10 +09002157 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09002158 private void queryDisplayStatus(final IHdmiControlCallback callback) {
2159 assertRunOnServiceThread();
2160 HdmiCecLocalDevicePlayback source = playback();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002161 if (source == null) {
2162 Slog.w(TAG, "Local playback device not available");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002163 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002164 return;
2165 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09002166 source.queryDisplayStatus(callback);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002167 }
2168
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002169 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
2170 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002171 try {
2172 listener.asBinder().linkToDeath(record, 0);
2173 } catch (RemoteException e) {
2174 Slog.w(TAG, "Listener already died");
2175 return;
2176 }
2177 synchronized (mLock) {
2178 mHotplugEventListenerRecords.add(record);
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002179 }
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002180
2181 // Inform the listener of the initial state of each HDMI port by generating
2182 // hotplug events.
2183 runOnServiceThread(new Runnable() {
2184 @Override
2185 public void run() {
2186 synchronized (mLock) {
2187 if (!mHotplugEventListenerRecords.contains(record)) return;
2188 }
Amy6a58a342019-05-16 18:24:24 -07002189 for (HdmiPortInfo port : getPortInfo()) {
Jinsuk Kim3cd30512014-12-04 11:05:09 +09002190 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
2191 mCecController.isConnected(port.getId()));
2192 synchronized (mLock) {
2193 invokeHotplugEventListenerLocked(listener, event);
2194 }
2195 }
2196 }
2197 });
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002198 }
2199
2200 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
2201 synchronized (mLock) {
2202 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2203 if (record.mListener.asBinder() == listener.asBinder()) {
2204 listener.asBinder().unlinkToDeath(record, 0);
2205 mHotplugEventListenerRecords.remove(record);
2206 break;
2207 }
2208 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +09002209 }
2210 }
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002211
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002212 private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002213 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
2214 try {
2215 listener.asBinder().linkToDeath(record, 0);
2216 } catch (RemoteException e) {
2217 Slog.w(TAG, "Listener already died");
2218 return;
2219 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002220 synchronized (mLock) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002221 mDeviceEventListenerRecords.add(record);
2222 }
2223 }
2224
Jungshik Jang61daf6b2014-08-08 11:38:28 +09002225 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002226 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002227 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002228 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002229 record.mListener.onStatusChanged(device, status);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002230 } catch (RemoteException e) {
2231 Slog.e(TAG, "Failed to report device event:" + e);
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002232 }
2233 }
Jinsuk Kim6d97f5b2014-06-16 11:41:42 +09002234 }
2235 }
2236
Jungshik Jangea67c182014-06-19 22:19:20 +09002237 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
2238 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
2239 listener);
2240 try {
2241 listener.asBinder().linkToDeath(record, 0);
2242 } catch (RemoteException e) {
2243 Slog.w(TAG, "Listener already died");
2244 return;
2245 }
2246 synchronized (mLock) {
Jungshik Jangea67c182014-06-19 22:19:20 +09002247 mSystemAudioModeChangeListenerRecords.add(record);
2248 }
2249 }
2250
2251 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
2252 synchronized (mLock) {
2253 for (SystemAudioModeChangeListenerRecord record :
2254 mSystemAudioModeChangeListenerRecords) {
2255 if (record.mListener.asBinder() == listener) {
2256 listener.asBinder().unlinkToDeath(record, 0);
2257 mSystemAudioModeChangeListenerRecords.remove(record);
2258 break;
2259 }
2260 }
Jungshik Jangea67c182014-06-19 22:19:20 +09002261 }
2262 }
2263
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002264 private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
Jungshik Jangf4249322014-08-21 14:17:05 +09002265 private final IHdmiInputChangeListener mListener;
2266
2267 public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
2268 mListener = listener;
2269 }
2270
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002271 @Override
2272 public void binderDied() {
2273 synchronized (mLock) {
Donghyun Chofbbeb3e2016-04-15 09:12:03 +09002274 if (mInputChangeListenerRecord == this) {
2275 mInputChangeListenerRecord = null;
2276 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002277 }
2278 }
2279 }
2280
2281 private void setInputChangeListener(IHdmiInputChangeListener listener) {
2282 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002283 mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002284 try {
2285 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
2286 } catch (RemoteException e) {
2287 Slog.w(TAG, "Listener already died");
2288 return;
2289 }
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002290 }
2291 }
2292
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002293 void invokeInputChangeListener(HdmiDeviceInfo info) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002294 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002295 if (mInputChangeListenerRecord != null) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002296 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002297 mInputChangeListenerRecord.mListener.onChanged(info);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09002298 } catch (RemoteException e) {
2299 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
2300 }
2301 }
2302 }
2303 }
2304
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002305 private void setHdmiRecordListener(IHdmiRecordListener listener) {
Jungshik Jangb6591b82014-07-23 16:10:23 +09002306 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002307 mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002308 try {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002309 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002310 } catch (RemoteException e) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002311 Slog.w(TAG, "Listener already died.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002312 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002313 }
2314 }
2315
2316 byte[] invokeRecordRequestListener(int recorderAddress) {
2317 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002318 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002319 try {
Jungshik Jangf4249322014-08-21 14:17:05 +09002320 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002321 } catch (RemoteException e) {
2322 Slog.w(TAG, "Failed to start record.", e);
Jungshik Jangb6591b82014-07-23 16:10:23 +09002323 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09002324 }
2325 return EmptyArray.BYTE;
2326 }
2327 }
2328
Jungshik Jang326aef02014-11-05 12:50:35 +09002329 void invokeOneTouchRecordResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002330 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002331 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002332 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002333 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002334 } catch (RemoteException e) {
2335 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
2336 }
2337 }
2338 }
2339 }
2340
Jungshik Jang326aef02014-11-05 12:50:35 +09002341 void invokeTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002342 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002343 if (mRecordListenerRecord != null) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002344 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002345 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002346 } catch (RemoteException e) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002347 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
2348 }
2349 }
2350 }
2351 }
2352
Jungshik Jang326aef02014-11-05 12:50:35 +09002353 void invokeClearTimerRecordingResult(int recorderAddress, int result) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002354 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002355 if (mRecordListenerRecord != null) {
Jungshik Jange5a93372014-07-25 13:41:14 +09002356 try {
Jungshik Jang326aef02014-11-05 12:50:35 +09002357 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
2358 result);
Jungshik Jange5a93372014-07-25 13:41:14 +09002359 } catch (RemoteException e) {
2360 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09002361 }
2362 }
2363 }
2364 }
2365
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +09002366 private void invokeCallback(IHdmiControlCallback callback, int result) {
2367 try {
2368 callback.onComplete(result);
2369 } catch (RemoteException e) {
2370 Slog.e(TAG, "Invoking callback failed:" + e);
2371 }
2372 }
Yuncheol Heo63a2e062014-05-27 23:06:01 +09002373
Jungshik Jangf4249322014-08-21 14:17:05 +09002374 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
Jungshik Jangea67c182014-06-19 22:19:20 +09002375 boolean enabled) {
2376 try {
2377 listener.onStatusChanged(enabled);
2378 } catch (RemoteException e) {
2379 Slog.e(TAG, "Invoking callback failed:" + e);
2380 }
2381 }
2382
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002383 private void announceHotplugEvent(int portId, boolean connected) {
2384 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002385 synchronized (mLock) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002386 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2387 invokeHotplugEventListenerLocked(record.mListener, event);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002388 }
2389 }
2390 }
2391
Jinsuk Kim4893c7e2014-06-19 14:13:22 +09002392 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
Jungshik Jang60cffce2014-06-12 18:03:04 +09002393 HdmiHotplugEvent event) {
2394 try {
2395 listener.onReceived(event);
2396 } catch (RemoteException e) {
2397 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
2398 }
Jungshik Jange81e1082014-06-05 15:37:59 +09002399 }
2400
Jinsuk Kimf98b9e82015-10-05 14:24:48 +09002401 public HdmiCecLocalDeviceTv tv() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002402 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
Jungshik Jang79c58a42014-06-16 16:45:36 +09002403 }
2404
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002405 boolean isTvDevice() {
Yuncheol Heob8d62e72014-09-22 19:53:41 +09002406 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
Yuncheol Heoe946ed82014-07-25 14:05:19 +09002407 }
2408
Amyb887fa02018-06-21 11:22:13 -07002409 boolean isAudioSystemDevice() {
2410 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2411 }
2412
Amy34037422018-09-06 13:21:08 -07002413 boolean isPlaybackDevice() {
2414 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
2415 }
2416
Amy066db152018-10-04 09:54:51 -07002417 boolean isSwitchDevice() {
2418 return SystemProperties.getBoolean(
Amy17ee20f2018-10-11 11:08:23 -07002419 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Amy066db152018-10-04 09:54:51 -07002420 }
2421
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002422 boolean isTvDeviceEnabled() {
2423 return isTvDevice() && tv() != null;
2424 }
2425
Amy34037422018-09-06 13:21:08 -07002426 protected HdmiCecLocalDevicePlayback playback() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002427 return (HdmiCecLocalDevicePlayback)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09002428 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jungshik Jang60cffce2014-06-12 18:03:04 +09002429 }
Jungshik Janga858d222014-06-23 17:17:47 +09002430
Shubangc480a712018-06-11 18:02:42 -07002431 public HdmiCecLocalDeviceAudioSystem audioSystem() {
2432 return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
2433 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2434 }
2435
Jungshik Janga858d222014-06-23 17:17:47 +09002436 AudioManager getAudioManager() {
2437 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
2438 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09002439
2440 boolean isControlEnabled() {
2441 synchronized (mLock) {
2442 return mHdmiControlEnabled;
2443 }
2444 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002445
Jungshik Jangf67113f2014-08-22 16:27:19 +09002446 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002447 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002448 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002449 return mPowerStatus;
2450 }
2451
Jungshik Jangf67113f2014-08-22 16:27:19 +09002452 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002453 boolean isPowerOnOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002454 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002455 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2456 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002457 }
2458
Jungshik Jangf67113f2014-08-22 16:27:19 +09002459 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002460 boolean isPowerStandbyOrTransient() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002461 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002462 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2463 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002464 }
2465
Jungshik Jangf67113f2014-08-22 16:27:19 +09002466 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +09002467 boolean isPowerStandby() {
Jungshik Jangf67113f2014-08-22 16:27:19 +09002468 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002469 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002470 }
2471
2472 @ServiceThreadOnly
2473 void wakeUp() {
2474 assertRunOnServiceThread();
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002475 mWakeUpMessageReceived = true;
Michael Wrighte3001042019-02-05 00:13:14 +00002476 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
2477 "android.server.hdmi:WAKE");
Yuncheol Heo38db6292014-07-01 14:15:14 +09002478 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2479 // the intent, the sequence will continue at onWakeUp().
2480 }
2481
2482 @ServiceThreadOnly
2483 void standby() {
2484 assertRunOnServiceThread();
Donghyun Cho02920a02016-10-11 17:17:34 +09002485 if (!canGoToStandby()) {
2486 return;
2487 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002488 mStandbyMessageReceived = true;
Jinsuk Kime26d8332015-01-09 08:55:41 +09002489 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002490 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2491 // the intent, the sequence will continue at onStandby().
2492 }
2493
Donghyun Choafd26a22016-12-23 15:53:28 +09002494 boolean isWakeUpMessageReceived() {
2495 return mWakeUpMessageReceived;
2496 }
2497
Amybf8a4662018-07-02 12:34:24 -07002498 @VisibleForTesting
2499 boolean isStandbyMessageReceived() {
2500 return mStandbyMessageReceived;
2501 }
2502
Yuncheol Heo38db6292014-07-01 14:15:14 +09002503 @ServiceThreadOnly
2504 private void onWakeUp() {
2505 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002506 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002507 if (mCecController != null) {
Jungshik Janga9f10622014-07-11 15:36:39 +09002508 if (mHdmiControlEnabled) {
Yuncheol Heofc44e4e2014-08-04 19:41:09 +09002509 int startReason = INITIATED_BY_SCREEN_ON;
2510 if (mWakeUpMessageReceived) {
2511 startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2512 }
2513 initializeCec(startReason);
Jungshik Janga9f10622014-07-11 15:36:39 +09002514 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002515 } else {
2516 Slog.i(TAG, "Device does not support HDMI-CEC.");
2517 }
2518 // TODO: Initialize MHL local devices.
2519 }
2520
2521 @ServiceThreadOnly
Amybf8a4662018-07-02 12:34:24 -07002522 @VisibleForTesting
2523 protected void onStandby(final int standbyAction) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002524 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002525 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
Yuncheol Heo0608b932014-10-13 16:39:18 +09002526 invokeVendorCommandListenersOnControlStateChanged(false,
2527 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
Amybf8a4662018-07-02 12:34:24 -07002528
2529 final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2530
2531 if (!isStandbyMessageReceived() && !canGoToStandby()) {
Donghyun Cho02920a02016-10-11 17:17:34 +09002532 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Amybf8a4662018-07-02 12:34:24 -07002533 for (HdmiCecLocalDevice device : devices) {
2534 device.onStandby(mStandbyMessageReceived, standbyAction);
2535 }
Donghyun Cho02920a02016-10-11 17:17:34 +09002536 return;
2537 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002538
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002539 disableDevices(new PendingActionClearedCallback() {
2540 @Override
2541 public void onCleared(HdmiCecLocalDevice device) {
2542 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2543 devices.remove(device);
2544 if (devices.isEmpty()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002545 onStandbyCompleted(standbyAction);
Yuncheol Heo4b542712014-07-30 20:31:06 +09002546 // We will not clear local devices here, since some OEM/SOC will keep passing
2547 // the received packets until the application processor enters to the sleep
2548 // actually.
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002549 }
2550 }
2551 });
2552 }
2553
Jinsuk Kime26d8332015-01-09 08:55:41 +09002554 private boolean canGoToStandby() {
2555 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2556 if (!device.canGoToStandby()) return false;
2557 }
2558 return true;
2559 }
2560
Terry Heo1ca0a432014-08-18 10:30:32 +09002561 @ServiceThreadOnly
2562 private void onLanguageChanged(String language) {
2563 assertRunOnServiceThread();
2564 mLanguage = language;
2565
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002566 if (isTvDeviceEnabled()) {
Terry Heo1ca0a432014-08-18 10:30:32 +09002567 tv().broadcastMenuLanguage(language);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002568 mCecController.setLanguage(language);
Terry Heo1ca0a432014-08-18 10:30:32 +09002569 }
2570 }
2571
Jungshik Jangf67113f2014-08-22 16:27:19 +09002572 @ServiceThreadOnly
2573 String getLanguage() {
2574 assertRunOnServiceThread();
2575 return mLanguage;
2576 }
2577
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002578 private void disableDevices(PendingActionClearedCallback callback) {
Jungshik Jang350e68d2014-08-19 18:56:21 +09002579 if (mCecController != null) {
2580 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2581 device.disableDevice(mStandbyMessageReceived, callback);
2582 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002583 }
Jungshik Jang350e68d2014-08-19 18:56:21 +09002584
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002585 mMhlController.clearAllLocalDevices();
Yuncheol Heo38db6292014-07-01 14:15:14 +09002586 }
2587
Yuncheol Heo38db6292014-07-01 14:15:14 +09002588 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002589 private void clearLocalDevices() {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002590 assertRunOnServiceThread();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002591 if (mCecController == null) {
2592 return;
2593 }
2594 mCecController.clearLogicalAddress();
2595 mCecController.clearLocalDevices();
2596 }
2597
2598 @ServiceThreadOnly
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002599 private void onStandbyCompleted(int standbyAction) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002600 assertRunOnServiceThread();
2601 Slog.v(TAG, "onStandbyCompleted");
2602
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002603 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09002604 return;
2605 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09002606 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
Yuncheol Heo38db6292014-07-01 14:15:14 +09002607 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +09002608 device.onStandby(mStandbyMessageReceived, standbyAction);
Yuncheol Heo38db6292014-07-01 14:15:14 +09002609 }
2610 mStandbyMessageReceived = false;
Amyb887fa02018-06-21 11:22:13 -07002611 if (!isAudioSystemDevice()) {
2612 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2613 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2614 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09002615 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002616
Jinsuk Kim119160a2014-07-07 18:48:10 +09002617 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2618 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2619 try {
2620 listener.asBinder().linkToDeath(record, 0);
2621 } catch (RemoteException e) {
2622 Slog.w(TAG, "Listener already died");
2623 return;
2624 }
2625 synchronized (mLock) {
2626 mVendorCommandListenerRecords.add(record);
2627 }
2628 }
2629
Yuncheol Heo0608b932014-10-13 16:39:18 +09002630 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2631 byte[] params, boolean hasVendorId) {
Jinsuk Kim119160a2014-07-07 18:48:10 +09002632 synchronized (mLock) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002633 if (mVendorCommandListenerRecords.isEmpty()) {
2634 return false;
2635 }
Jinsuk Kim119160a2014-07-07 18:48:10 +09002636 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2637 if (record.mDeviceType != deviceType) {
2638 continue;
2639 }
2640 try {
Yuncheol Heo0608b932014-10-13 16:39:18 +09002641 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
Jinsuk Kim119160a2014-07-07 18:48:10 +09002642 } catch (RemoteException e) {
2643 Slog.e(TAG, "Failed to notify vendor command reception", e);
2644 }
2645 }
Jinsuk Kimd4a94db2014-09-12 13:51:10 +09002646 return true;
Jinsuk Kim119160a2014-07-07 18:48:10 +09002647 }
2648 }
2649
Yuncheol Heo0608b932014-10-13 16:39:18 +09002650 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2651 synchronized (mLock) {
2652 if (mVendorCommandListenerRecords.isEmpty()) {
2653 return false;
2654 }
2655 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2656 try {
2657 record.mListener.onControlStateChanged(enabled, reason);
2658 } catch (RemoteException e) {
2659 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2660 }
2661 }
2662 return true;
2663 }
2664 }
2665
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002666 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2667 HdmiMhlVendorCommandListenerRecord record =
2668 new HdmiMhlVendorCommandListenerRecord(listener);
Jungshik Jangf4249322014-08-21 14:17:05 +09002669 try {
2670 listener.asBinder().linkToDeath(record, 0);
2671 } catch (RemoteException e) {
2672 Slog.w(TAG, "Listener already died.");
2673 return;
2674 }
2675
2676 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002677 mMhlVendorCommandListenerRecords.add(record);
Jungshik Jangf4249322014-08-21 14:17:05 +09002678 }
2679 }
2680
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002681 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002682 synchronized (mLock) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002683 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
Jungshik Jangf4249322014-08-21 14:17:05 +09002684 try {
2685 record.mListener.onReceived(portId, offest, length, data);
2686 } catch (RemoteException e) {
Jinsuk Kimb3fbf9d2014-09-12 10:41:40 +09002687 Slog.e(TAG, "Failed to notify MHL vendor command", e);
Jungshik Jangf4249322014-08-21 14:17:05 +09002688 }
2689 }
2690 }
2691 }
2692
Donghyun Chob3515642017-03-02 13:47:40 +09002693 void setStandbyMode(boolean isStandbyModeOn) {
2694 assertRunOnServiceThread();
2695 if (isPowerOnOrTransient() && isStandbyModeOn) {
2696 mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2697 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2698 if (playback() != null) {
2699 playback().sendStandby(0 /* unused */);
2700 }
2701 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
Michael Wrighte3001042019-02-05 00:13:14 +00002702 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
2703 "android.server.hdmi:WAKE");
Donghyun Chob3515642017-03-02 13:47:40 +09002704 if (playback() != null) {
2705 oneTouchPlay(new IHdmiControlCallback.Stub() {
2706 @Override
2707 public void onComplete(int result) {
2708 if (result != HdmiControlManager.RESULT_SUCCESS) {
2709 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
2710 }
2711 }
2712 });
2713 }
2714 }
2715 }
2716
Jinsuk Kim4d43d932014-07-03 16:43:58 +09002717 boolean isProhibitMode() {
2718 synchronized (mLock) {
2719 return mProhibitMode;
2720 }
2721 }
2722
2723 void setProhibitMode(boolean enabled) {
2724 synchronized (mLock) {
2725 mProhibitMode = enabled;
2726 }
2727 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002728
Amy489454f2019-01-24 19:06:57 -08002729 boolean isSystemAudioActivated() {
2730 synchronized (mLock) {
2731 return mSystemAudioActivated;
2732 }
2733 }
2734
2735 void setSystemAudioActivated(boolean on) {
2736 synchronized (mLock) {
2737 mSystemAudioActivated = on;
2738 }
2739 }
2740
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002741 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +09002742 void setCecOption(int key, boolean value) {
Jinsuk Kim50084862014-08-07 13:11:40 +09002743 assertRunOnServiceThread();
2744 mCecController.setOption(key, value);
2745 }
2746
2747 @ServiceThreadOnly
2748 void setControlEnabled(boolean enabled) {
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002749 assertRunOnServiceThread();
2750
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002751 synchronized (mLock) {
2752 mHdmiControlEnabled = enabled;
2753 }
2754
2755 if (enabled) {
Yuncheol Heof1702482014-11-27 19:52:01 +09002756 enableHdmiControlService();
2757 return;
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002758 }
Yuncheol Heof1702482014-11-27 19:52:01 +09002759 // Call the vendor handler before the service is disabled.
2760 invokeVendorCommandListenersOnControlStateChanged(false,
2761 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2762 // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2763 // a chance to run.
2764 runOnServiceThread(new Runnable() {
2765 @Override
2766 public void run() {
2767 disableHdmiControlService();
2768 }
2769 });
2770 return;
2771 }
2772
2773 @ServiceThreadOnly
2774 private void enableHdmiControlService() {
Amy718e41e2018-08-17 17:23:37 -07002775 mCecController.setOption(OptionKey.ENABLE_CEC, true);
Donghyun Chobc6e3722016-11-04 05:25:52 +09002776 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
Yuncheol Heof1702482014-11-27 19:52:01 +09002777 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2778
2779 initializeCec(INITIATED_BY_ENABLE_CEC);
2780 }
2781
2782 @ServiceThreadOnly
2783 private void disableHdmiControlService() {
2784 disableDevices(new PendingActionClearedCallback() {
2785 @Override
2786 public void onCleared(HdmiCecLocalDevice device) {
2787 assertRunOnServiceThread();
2788 mCecController.flush(new Runnable() {
2789 @Override
2790 public void run() {
Donghyun Chobc6e3722016-11-04 05:25:52 +09002791 mCecController.setOption(OptionKey.ENABLE_CEC, false);
Amy718e41e2018-08-17 17:23:37 -07002792 mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
Yuncheol Heof1702482014-11-27 19:52:01 +09002793 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2794 clearLocalDevices();
2795 }
2796 });
2797 }
2798 });
Jungshik Jang4fc1d102014-07-09 19:24:50 +09002799 }
Jungshik Jang867b4e02014-08-12 13:41:30 +09002800
2801 @ServiceThreadOnly
2802 void setActivePortId(int portId) {
2803 assertRunOnServiceThread();
2804 mActivePortId = portId;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002805
2806 // Resets last input for MHL, which stays valid only after the MHL device was selected,
2807 // and no further switching is done.
2808 setLastInputForMhl(Constants.INVALID_PORT_ID);
Jungshik Jang867b4e02014-08-12 13:41:30 +09002809 }
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002810
Amy123ec402018-09-25 10:56:31 -07002811 ActiveSource getActiveSource() {
2812 synchronized (mLock) {
2813 return mActiveSource;
2814 }
2815 }
2816
2817 void setActiveSource(int logicalAddress, int physicalAddress) {
2818 synchronized (mLock) {
2819 mActiveSource.logicalAddress = logicalAddress;
2820 mActiveSource.physicalAddress = physicalAddress;
2821 }
Amy02cf1ab2019-04-30 13:25:41 -07002822 // If the current device is a source device, check if the current Active Source matches
2823 // the local device info. Set mIsActiveSource of the local device accordingly.
2824 for (HdmiCecLocalDevice device : getAllLocalDevices()) {
2825 // mIsActiveSource only exists in source device, ignore this setting if the current
2826 // device is not an HdmiCecLocalDeviceSource.
2827 if (!(device instanceof HdmiCecLocalDeviceSource)) {
2828 continue;
2829 }
2830 if (logicalAddress == device.getDeviceInfo().getLogicalAddress()
2831 && physicalAddress == getPhysicalAddress()) {
2832 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(true);
2833 } else {
2834 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(false);
2835 }
2836 }
Amy123ec402018-09-25 10:56:31 -07002837 }
2838
2839 // This method should only be called when the device can be the active source
2840 // and all the device types call into this method.
2841 // For example, when receiving broadcast messages, all the device types will call this
2842 // method but only one of them will be the Active Source.
2843 protected void setAndBroadcastActiveSource(
Amyb9d7f432018-11-30 15:08:30 -08002844 int physicalAddress, int deviceType, int source) {
Amy123ec402018-09-25 10:56:31 -07002845 // If the device has both playback and audio system logical addresses,
2846 // playback will claim active source. Otherwise audio system will.
2847 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
2848 HdmiCecLocalDevicePlayback playback = playback();
2849 playback.setIsActiveSource(true);
2850 playback.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08002851 playback.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07002852 setActiveSource(playback.mAddress, physicalAddress);
2853 }
2854
2855 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
2856 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
2857 if (playback() != null) {
2858 audioSystem.setIsActiveSource(false);
2859 } else {
2860 audioSystem.setIsActiveSource(true);
2861 audioSystem.wakeUpIfActiveSource();
Amyb9d7f432018-11-30 15:08:30 -08002862 audioSystem.maySendActiveSource(source);
Amy123ec402018-09-25 10:56:31 -07002863 setActiveSource(audioSystem.mAddress, physicalAddress);
2864 }
2865 }
2866 }
2867
2868 // This method should only be called when the device can be the active source
2869 // and only one of the device types calls into this method.
2870 // For example, when receiving One Touch Play, only playback device handles it
2871 // and this method updates Active Source in all the device types sharing the same
2872 // Physical Address.
2873 protected void setAndBroadcastActiveSourceFromOneDeviceType(
2874 int sourceAddress, int physicalAddress) {
2875 // If the device has both playback and audio system logical addresses,
2876 // playback will claim active source. Otherwise audio system will.
2877 HdmiCecLocalDevicePlayback playback = playback();
2878 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
2879 if (playback != null) {
2880 playback.setIsActiveSource(true);
2881 playback.wakeUpIfActiveSource();
2882 playback.maySendActiveSource(sourceAddress);
2883 if (audioSystem != null) {
2884 audioSystem.setIsActiveSource(false);
2885 }
2886 setActiveSource(playback.mAddress, physicalAddress);
2887 } else {
2888 if (audioSystem != null) {
2889 audioSystem.setIsActiveSource(true);
2890 audioSystem.wakeUpIfActiveSource();
2891 audioSystem.maySendActiveSource(sourceAddress);
2892 setActiveSource(audioSystem.mAddress, physicalAddress);
2893 }
2894 }
2895 }
2896
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002897 @ServiceThreadOnly
2898 void setLastInputForMhl(int portId) {
2899 assertRunOnServiceThread();
2900 mLastInputMhl = portId;
2901 }
2902
2903 @ServiceThreadOnly
2904 int getLastInputForMhl() {
2905 assertRunOnServiceThread();
2906 return mLastInputMhl;
2907 }
2908
2909 /**
2910 * Performs input change, routing control for MHL device.
2911 *
2912 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2913 * @param contentOn {@code true} if RAP data is content on; otherwise false
2914 */
2915 @ServiceThreadOnly
2916 void changeInputForMhl(int portId, boolean contentOn) {
2917 assertRunOnServiceThread();
Jinsuk Kimde7a4242014-12-05 12:05:27 +09002918 if (tv() == null) return;
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002919 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002920 if (portId != Constants.INVALID_PORT_ID) {
2921 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2922 @Override
2923 public void onComplete(int result) throws RemoteException {
2924 // Keep the last input to switch back later when RAP[ContentOff] is received.
2925 // This effectively sets the port to invalid one if the switching is for
2926 // RAP[ContentOff].
2927 setLastInputForMhl(lastInput);
2928 }
2929 });
2930 }
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002931 // MHL device is always directly connected to the port. Update the active port ID to avoid
2932 // unnecessary post-routing control task.
2933 tv().setActivePortId(portId);
2934
2935 // The port is either the MHL-enabled port where the mobile device is connected, or
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002936 // the last port to go back to when turnoff command is received. Note that the last port
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002937 // may not be the MHL-enabled one. In this case the device info to be passed to
2938 // input change listener should be the one describing the corresponding HDMI port.
Jinsuk Kim3b9309a2014-09-12 15:10:33 +09002939 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
Jinsuk Kimcb8661c2015-01-19 12:39:06 +09002940 HdmiDeviceInfo info = (device != null) ? device.getInfo()
2941 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +09002942 invokeInputChangeListener(info);
2943 }
2944
2945 void setMhlInputChangeEnabled(boolean enabled) {
Jinsuk Kimf286b4d2014-08-26 19:22:17 +09002946 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
Yuncheol Heo08a1be82014-08-12 20:58:41 +09002947
2948 synchronized (mLock) {
2949 mMhlInputChangeEnabled = enabled;
2950 }
2951 }
2952
2953 boolean isMhlInputChangeEnabled() {
2954 synchronized (mLock) {
2955 return mMhlInputChangeEnabled;
2956 }
2957 }
Jungshik Jang339227d2014-08-25 15:37:20 +09002958
2959 @ServiceThreadOnly
2960 void displayOsd(int messageId) {
2961 assertRunOnServiceThread();
2962 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2963 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2964 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2965 HdmiControlService.PERMISSION);
2966 }
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002967
2968 @ServiceThreadOnly
2969 void displayOsd(int messageId, int extra) {
2970 assertRunOnServiceThread();
2971 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2972 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +09002973 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09002974 getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2975 HdmiControlService.PERMISSION);
2976 }
Jungshik Jang0792d372014-04-23 17:57:26 +09002977}