blob: 03704aad59c27685c4a8a7bf0772b32608158e62 [file] [log] [blame]
Nick Chalkob89d6ff2018-05-25 10:29:01 -07001/*
2 * Copyright (C) 2018 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 */
16package com.android.server.hdmi;
17
Amy6506bd62018-07-02 17:29:36 -070018import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
19import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
20import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
21
Nick Chalko167529c2018-07-18 13:11:31 -070022import android.annotation.Nullable;
Amy225d55a2018-09-06 11:03:51 -070023import android.content.Intent;
shubangd932cb52018-09-14 17:55:16 -070024import android.hardware.hdmi.HdmiControlManager;
Nick Chalkob89d6ff2018-05-25 10:29:01 -070025import android.hardware.hdmi.HdmiDeviceInfo;
Amy59176da2018-10-12 16:30:54 -070026import android.hardware.hdmi.HdmiPortInfo;
shubangd932cb52018-09-14 17:55:16 -070027import android.hardware.hdmi.IHdmiControlCallback;
Nick Chalko167529c2018-07-18 13:11:31 -070028import android.media.AudioDeviceInfo;
Nick Chalkod70c4132018-08-27 16:22:53 -070029import android.media.AudioFormat;
Amy2a6c3dc2018-06-05 17:31:55 -070030import android.media.AudioManager;
Shubang81170da2018-07-12 18:02:52 -070031import android.media.AudioSystem;
Amy225d55a2018-09-06 11:03:51 -070032import android.media.tv.TvContract;
Nick Chalkob89d6ff2018-05-25 10:29:01 -070033import android.os.SystemProperties;
Amy0c2e29f2018-10-23 12:17:52 -070034import android.provider.Settings.Global;
Amy89e93af2018-10-15 09:56:54 -070035import android.util.Slog;
36import android.util.SparseArray;
Nick Chalko167529c2018-07-18 13:11:31 -070037
Amy87eda822018-06-06 17:56:39 -070038import com.android.internal.annotations.GuardedBy;
Amy6506bd62018-07-02 17:29:36 -070039import com.android.internal.annotations.VisibleForTesting;
Nick Chalkob9e48e22018-10-23 06:59:39 -070040import com.android.internal.util.IndentingPrintWriter;
Nick Chalko167529c2018-07-18 13:11:31 -070041import com.android.server.hdmi.Constants.AudioCodec;
Amyc00cd4e2018-10-16 20:21:33 -070042import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
Amy4a922662018-06-05 17:31:55 -070043import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Amyd984bc62018-11-28 18:53:54 -080044import com.android.server.hdmi.HdmiUtils.CodecSad;
45import com.android.server.hdmi.HdmiUtils.DeviceConfig;
Nick Chalkob89d6ff2018-05-25 10:29:01 -070046
Amyd984bc62018-11-28 18:53:54 -080047import org.xmlpull.v1.XmlPullParserException;
48
49import java.io.File;
50import java.io.FileInputStream;
51import java.io.IOException;
52import java.io.InputStream;
Amy89e93af2018-10-15 09:56:54 -070053import java.io.UnsupportedEncodingException;
Nick Chalkod70c4132018-08-27 16:22:53 -070054import java.util.ArrayList;
55import java.util.Arrays;
Amyec126a52018-10-30 16:51:14 -070056import java.util.Collections;
Amy34037422018-09-06 13:21:08 -070057import java.util.HashMap;
Amyc00cd4e2018-10-16 20:21:33 -070058import java.util.List;
Nick Chalkod70c4132018-08-27 16:22:53 -070059import java.util.stream.Collectors;
60
Amy34037422018-09-06 13:21:08 -070061
Nick Chalkob89d6ff2018-05-25 10:29:01 -070062/**
Nick Chalkof32fcea2018-07-17 16:17:40 -070063 * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
64 * system.
Nick Chalkob89d6ff2018-05-25 10:29:01 -070065 */
Amy848a9f22018-08-27 17:21:26 -070066public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
Nick Chalkob89d6ff2018-05-25 10:29:01 -070067
Amy4a922662018-06-05 17:31:55 -070068 private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
69
Amy87eda822018-06-06 17:56:39 -070070 // Whether System audio mode is activated or not.
71 // This becomes true only when all system audio sequences are finished.
72 @GuardedBy("mLock")
73 private boolean mSystemAudioActivated;
74
Amy9b91e8c2018-06-11 17:26:26 -070075 // Whether the System Audio Control feature is enabled or not. True by default.
76 @GuardedBy("mLock")
77 private boolean mSystemAudioControlFeatureEnabled;
Nick Chalkof32fcea2018-07-17 16:17:40 -070078
Shubangec668962018-07-13 19:03:19 -070079 private boolean mTvSystemAudioModeSupport;
80
Shubang81170da2018-07-12 18:02:52 -070081 // Whether ARC is available or not. "true" means that ARC is established between TV and
82 // AVR as audio receiver.
Nick Chalko167529c2018-07-18 13:11:31 -070083 @ServiceThreadOnly private boolean mArcEstablished = false;
Shubang81170da2018-07-12 18:02:52 -070084
Amy34037422018-09-06 13:21:08 -070085 // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
86 // when ARC is using TvInput.
Amy225d55a2018-09-06 11:03:51 -070087 private boolean mArcIntentUsed = SystemProperties
88 .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
Amy6a6d6182018-08-20 17:38:47 -070089
Amy34037422018-09-06 13:21:08 -070090 // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
91 // accept input switching request from HDMI devices. Requests for which the corresponding
92 // input ID is not yet registered by TV input framework need to be buffered for delayed
93 // processing.
94 private final HashMap<Integer, String> mTvInputs = new HashMap<>();
95
Amyec126a52018-10-30 16:51:14 -070096 // Copy of mDeviceInfos to guarantee thread-safety.
97 @GuardedBy("mLock")
98 private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
99
Amy5c6026b2018-10-12 14:42:43 -0700100 // Map-like container of all cec devices.
101 // device id is used as key of container.
102 private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
103
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700104 protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
105 super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
Amy0c2e29f2018-10-23 12:17:52 -0700106 mRoutingControlFeatureEnabled =
Amy77e672c2018-10-31 15:55:40 -0700107 mService.readBooleanSetting(Global.HDMI_CEC_SWITCH_ENABLED, false);
Amy79db52f2018-10-23 12:45:17 -0700108 mSystemAudioControlFeatureEnabled =
109 mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
Amy00638112018-10-31 17:47:17 -0700110 // TODO(amyjojo): Maintain a portId to TvinputId map.
111 mTvInputs.put(2, "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
112 mTvInputs.put(4, "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
113 mTvInputs.put(1, "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700114 }
115
Amyd984bc62018-11-28 18:53:54 -0800116 private static final String SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH = "/vendor/etc/sadConfig.xml";
117
Amy5c6026b2018-10-12 14:42:43 -0700118 /**
119 * Called when a device is newly added or a new device is detected or
Amy1e4a8cc2018-10-15 10:18:14 -0700120 * an existing device is updated.
Amy5c6026b2018-10-12 14:42:43 -0700121 *
122 * @param info device info of a new device.
123 */
124 @ServiceThreadOnly
125 final void addCecDevice(HdmiDeviceInfo info) {
126 assertRunOnServiceThread();
127 HdmiDeviceInfo old = addDeviceInfo(info);
Amy89e93af2018-10-15 09:56:54 -0700128 if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
Amy5c6026b2018-10-12 14:42:43 -0700129 // The addition of the device itself should not be notified.
Amy89e93af2018-10-15 09:56:54 -0700130 // Note that different logical address could still be the same local device.
Amy5c6026b2018-10-12 14:42:43 -0700131 return;
132 }
133 if (old == null) {
134 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
135 } else if (!old.equals(info)) {
136 invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
137 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
138 }
139 }
140
141 /**
142 * Called when a device is removed or removal of device is detected.
143 *
144 * @param address a logical address of a device to be removed
145 */
146 @ServiceThreadOnly
147 final void removeCecDevice(int address) {
148 assertRunOnServiceThread();
149 HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
150
151 mCecMessageCache.flushMessagesFrom(address);
152 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
153 }
154
155 /**
Amy89e93af2018-10-15 09:56:54 -0700156 * Called when a device is updated.
157 *
158 * @param info device info of the updating device.
159 */
160 @ServiceThreadOnly
161 final void updateCecDevice(HdmiDeviceInfo info) {
162 assertRunOnServiceThread();
163 HdmiDeviceInfo old = addDeviceInfo(info);
164
165 if (old == null) {
166 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
167 } else if (!old.equals(info)) {
168 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
169 }
170 }
171
172 /**
173 * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
Amy5c6026b2018-10-12 14:42:43 -0700174 * logical address as new device info's.
175 *
176 * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
177 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
178 * that has the same logical address as new one has.
179 */
180 @ServiceThreadOnly
Amy89e93af2018-10-15 09:56:54 -0700181 @VisibleForTesting
182 protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
Amy5c6026b2018-10-12 14:42:43 -0700183 assertRunOnServiceThread();
184 HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
185 if (oldDeviceInfo != null) {
186 removeDeviceInfo(deviceInfo.getId());
187 }
188 mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
Amyec126a52018-10-30 16:51:14 -0700189 updateSafeDeviceInfoList();
Amy5c6026b2018-10-12 14:42:43 -0700190 return oldDeviceInfo;
191 }
192
193 /**
194 * Remove a device info corresponding to the given {@code logicalAddress}.
195 * It returns removed {@link HdmiDeviceInfo} if exists.
196 *
197 * @param id id of device to be removed
198 * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
199 */
200 @ServiceThreadOnly
201 private HdmiDeviceInfo removeDeviceInfo(int id) {
202 assertRunOnServiceThread();
203 HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
204 if (deviceInfo != null) {
205 mDeviceInfos.remove(id);
206 }
Amyec126a52018-10-30 16:51:14 -0700207 updateSafeDeviceInfoList();
Amy5c6026b2018-10-12 14:42:43 -0700208 return deviceInfo;
209 }
210
211 /**
212 * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
213 *
214 * @param logicalAddress logical address of the device to be retrieved
215 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
216 * Returns null if no logical address matched
217 */
218 @ServiceThreadOnly
219 HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
220 assertRunOnServiceThread();
221 return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
222 }
223
Amyec126a52018-10-30 16:51:14 -0700224 @ServiceThreadOnly
225 private void updateSafeDeviceInfoList() {
226 assertRunOnServiceThread();
227 List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
228 synchronized (mLock) {
229 mSafeAllDeviceInfos = copiedDevices;
230 }
231 }
232
233 @GuardedBy("mLock")
234 List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
235 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
236 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
237 infoList.add(info);
238 }
239 return infoList;
240 }
241
Amy5c6026b2018-10-12 14:42:43 -0700242 private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
243 mService.invokeDeviceEventListeners(info, status);
244 }
245
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700246 @Override
Amy4a922662018-06-05 17:31:55 -0700247 @ServiceThreadOnly
Amy59176da2018-10-12 16:30:54 -0700248 void onHotplug(int portId, boolean connected) {
249 assertRunOnServiceThread();
250 if (connected) {
251 mService.wakeUp();
252 }
253 if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
254 mCecMessageCache.flushAll();
255 } else {
256 if (connected) {
Amyc00cd4e2018-10-16 20:21:33 -0700257 launchDeviceDiscovery();
Amy59176da2018-10-12 16:30:54 -0700258 } else {
259 // TODO(amyjojo): remove device from mDeviceInfo
260 }
261 }
262 }
263
264 @Override
265 @ServiceThreadOnly
Amyb887fa02018-06-21 11:22:13 -0700266 protected void onStandby(boolean initiatedByCec, int standbyAction) {
267 assertRunOnServiceThread();
Shubangec668962018-07-13 19:03:19 -0700268 mTvSystemAudioModeSupport = false;
Amy6506bd62018-07-02 17:29:36 -0700269 // Record the last state of System Audio Control before going to standby
270 synchronized (mLock) {
Amy59c06c12019-01-18 15:35:15 -0800271 mService.writeStringSystemProperty(
Nick Chalkof32fcea2018-07-17 16:17:40 -0700272 Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
273 mSystemAudioActivated ? "true" : "false");
Amy6506bd62018-07-02 17:29:36 -0700274 }
Shubangbc2aab32018-07-19 16:41:17 -0700275 terminateSystemAudioMode();
Amyb887fa02018-06-21 11:22:13 -0700276 }
277
278 @Override
279 @ServiceThreadOnly
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700280 protected void onAddressAllocated(int logicalAddress, int reason) {
281 assertRunOnServiceThread();
Amyb9d7f432018-11-30 15:08:30 -0800282 if (reason == mService.INITIATED_BY_ENABLE_CEC) {
283 mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
284 getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
285 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700286 mService.sendCecCommand(
287 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
288 mAddress, mService.getPhysicalAddress(), mDeviceType));
289 mService.sendCecCommand(
290 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, mService.getVendorId()));
291 int systemAudioControlOnPowerOnProp =
292 SystemProperties.getInt(
293 PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
294 ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
295 boolean lastSystemAudioControlStatus =
296 SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
Amy6506bd62018-07-02 17:29:36 -0700297 systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
Amyc00cd4e2018-10-16 20:21:33 -0700298 clearDeviceInfoList();
299 launchDeviceDiscovery();
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700300 startQueuedActions();
301 }
302
Shubang Lu00b976a2018-08-01 18:11:46 -0700303 @Override
304 protected int findKeyReceiverAddress() {
Amy2a5e2452018-11-29 20:32:20 -0800305 if (getActiveSource().isValid()) {
306 return getActiveSource().logicalAddress;
307 }
308 return Constants.ADDR_INVALID;
Shubang Lu00b976a2018-08-01 18:11:46 -0700309 }
310
Amy6506bd62018-07-02 17:29:36 -0700311 @VisibleForTesting
312 protected void systemAudioControlOnPowerOn(
Nick Chalkof32fcea2018-07-17 16:17:40 -0700313 int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
314 if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
315 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
Amy79db52f2018-10-23 12:45:17 -0700316 && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
Amy6506bd62018-07-02 17:29:36 -0700317 addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
318 }
319 }
320
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700321 @Override
Amy4a922662018-06-05 17:31:55 -0700322 @ServiceThreadOnly
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700323 protected int getPreferredAddress() {
324 assertRunOnServiceThread();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700325 return SystemProperties.getInt(
Amy06dc4cd2018-08-09 18:22:40 -0700326 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700327 }
328
329 @Override
Amy4a922662018-06-05 17:31:55 -0700330 @ServiceThreadOnly
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700331 protected void setPreferredAddress(int addr) {
332 assertRunOnServiceThread();
Amy59c06c12019-01-18 15:35:15 -0800333 mService.writeStringSystemProperty(
Nick Chalkof32fcea2018-07-17 16:17:40 -0700334 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700335 }
Amy4a922662018-06-05 17:31:55 -0700336
337 @Override
338 @ServiceThreadOnly
Amy89e93af2018-10-15 09:56:54 -0700339 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
340 assertRunOnServiceThread();
341 int path = HdmiUtils.twoBytesToInt(message.getParams());
342 int address = message.getSource();
343 int type = message.getParams()[2];
344
345 // Ignore if [Device Discovery Action] is going on.
346 if (hasAction(DeviceDiscoveryAction.class)) {
347 Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
348 return true;
349 }
350
351 // Update the device info with TIF, note that the same device info could have added in
352 // device discovery and we do not want to override it with default OSD name. Therefore we
353 // need the following check to skip redundant device info updating.
354 HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
355 if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
356 addCecDevice(new HdmiDeviceInfo(
357 address, path, mService.pathToPortId(path), type,
358 Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
359 // if we are adding a new device info, send out a give osd name command
360 // to update the name of the device in TIF
361 mService.sendCecCommand(
362 HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
363 return true;
364 }
365
366 Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
367 return true;
368 }
369
370 @Override
Amy1e4a8cc2018-10-15 10:18:14 -0700371 protected boolean handleReportPowerStatus(HdmiCecMessage command) {
372 int newStatus = command.getParams()[0] & 0xFF;
373 updateDevicePowerStatus(command.getSource(), newStatus);
374 return true;
375 }
376
377 @Override
Amy89e93af2018-10-15 09:56:54 -0700378 @ServiceThreadOnly
379 protected boolean handleSetOsdName(HdmiCecMessage message) {
380 int source = message.getSource();
381 String osdName;
382 HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
383 // If the device is not in device list, ignore it.
384 if (deviceInfo == null) {
385 Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
386 return true;
387 }
388 try {
389 osdName = new String(message.getParams(), "US-ASCII");
390 } catch (UnsupportedEncodingException e) {
391 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
392 return true;
393 }
394
395 if (deviceInfo.getDisplayName().equals(osdName)) {
396 Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
397 return true;
398 }
399
400 Slog.d(TAG, "Updating device OSD name from "
401 + deviceInfo.getDisplayName()
402 + " to " + osdName);
403 updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
404 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
405 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
406 return true;
407 }
408
409 @Override
410 @ServiceThreadOnly
Amy4a922662018-06-05 17:31:55 -0700411 protected boolean handleReportAudioStatus(HdmiCecMessage message) {
412 assertRunOnServiceThread();
413 // TODO(amyjojo): implement report audio status handler
414 HdmiLogger.debug(TAG + "Stub handleReportAudioStatus");
415 return true;
416 }
417
418 @Override
419 @ServiceThreadOnly
420 protected boolean handleInitiateArc(HdmiCecMessage message) {
421 assertRunOnServiceThread();
422 // TODO(amyjojo): implement initiate arc handler
423 HdmiLogger.debug(TAG + "Stub handleInitiateArc");
424 return true;
425 }
426
427 @Override
428 @ServiceThreadOnly
429 protected boolean handleReportArcInitiate(HdmiCecMessage message) {
430 assertRunOnServiceThread();
431 // TODO(amyjojo): implement report arc initiate handler
432 HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
433 return true;
434 }
435
436 @Override
437 @ServiceThreadOnly
438 protected boolean handleReportArcTermination(HdmiCecMessage message) {
439 assertRunOnServiceThread();
440 // TODO(amyjojo): implement report arc terminate handler
441 HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
442 return true;
443 }
Amy2a6c3dc2018-06-05 17:31:55 -0700444
445 @Override
446 @ServiceThreadOnly
447 protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
448 assertRunOnServiceThread();
Amy79db52f2018-10-23 12:45:17 -0700449 if (isSystemAudioControlFeatureEnabled()) {
450 reportAudioStatus(message.getSource());
451 } else {
452 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
453 }
Amy2a6c3dc2018-06-05 17:31:55 -0700454 return true;
455 }
456
Amy87eda822018-06-06 17:56:39 -0700457 @Override
458 @ServiceThreadOnly
459 protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
460 assertRunOnServiceThread();
Amy27ae4032019-01-31 16:42:16 -0800461 // If the audio system is initiating the system audio mode on and TV asks the sam status at
462 // the same time, respond with true. Since we know TV supports sam in this situation.
463 // If the query comes from STB, we should respond with the current sam status and the STB
464 // should listen to the <Set System Audio Mode> broadcasting.
465 boolean isSystemAudioModeOnOrTurningOn = isSystemAudioActivated();
466 if (!isSystemAudioModeOnOrTurningOn
467 && message.getSource() == Constants.ADDR_TV
468 && hasAction(SystemAudioInitiationActionFromAvr.class)) {
469 isSystemAudioModeOnOrTurningOn = true;
470 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700471 mService.sendCecCommand(
472 HdmiCecMessageBuilder.buildReportSystemAudioMode(
Amy27ae4032019-01-31 16:42:16 -0800473 mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn));
Amy87eda822018-06-06 17:56:39 -0700474 return true;
475 }
476
Amy4e7ff1a2018-06-07 16:24:31 -0700477 @Override
478 @ServiceThreadOnly
479 protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
480 assertRunOnServiceThread();
Amy03afe482018-09-18 16:57:45 -0700481 removeAction(ArcInitiationActionFromAvr.class);
Amy59c06c12019-01-18 15:35:15 -0800482 if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
Amy77c7c982018-07-23 18:27:36 -0700483 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
484 } else if (!isDirectConnectToTv()) {
485 HdmiLogger.debug("AVR device is not directly connected with TV");
486 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
487 } else {
488 addAndStartAction(new ArcInitiationActionFromAvr(this));
489 }
Amy4e7ff1a2018-06-07 16:24:31 -0700490 return true;
491 }
492
493 @Override
494 @ServiceThreadOnly
495 protected boolean handleRequestArcTermination(HdmiCecMessage message) {
496 assertRunOnServiceThread();
Amy77c7c982018-07-23 18:27:36 -0700497 if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
498 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
499 } else if (!isArcEnabled()) {
500 HdmiLogger.debug("ARC is not established between TV and AVR device");
501 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
502 } else {
Amy03afe482018-09-18 16:57:45 -0700503 removeAction(ArcTerminationActionFromAvr.class);
Amy77c7c982018-07-23 18:27:36 -0700504 addAndStartAction(new ArcTerminationActionFromAvr(this));
505 }
Amy4e7ff1a2018-06-07 16:24:31 -0700506 return true;
507 }
508
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700509 @ServiceThreadOnly
510 protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
511 assertRunOnServiceThread();
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700512 HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
Nick Chalko167529c2018-07-18 13:11:31 -0700513 if (!isSystemAudioControlFeatureEnabled()) {
514 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
515 return true;
516 }
517 if (!isSystemAudioActivated()) {
518 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
519 return true;
520 }
Amyd984bc62018-11-28 18:53:54 -0800521
522 List<DeviceConfig> config = null;
523 File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH);
524 if (file.exists()) {
525 try {
526 InputStream in = new FileInputStream(file);
527 config = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in);
528 in.close();
529 } catch (IOException e) {
530 Slog.e(TAG, "Error reading file: " + file, e);
531 } catch (XmlPullParserException e) {
532 Slog.e(TAG, "Unable to parse file: " + file, e);
533 }
Nick Chalko167529c2018-07-18 13:11:31 -0700534 }
Amyd984bc62018-11-28 18:53:54 -0800535
Nick Chalko167529c2018-07-18 13:11:31 -0700536 @AudioCodec int[] audioFormatCodes = parseAudioFormatCodes(message.getParams());
Amyd984bc62018-11-28 18:53:54 -0800537 byte[] sadBytes;
538 if (config != null && config.size() > 0) {
539 sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioFormatCodes);
540 } else {
541 AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
542 if (deviceInfo == null) {
543 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
544 return true;
545 }
546
547 sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes);
548 }
549
Nick Chalko167529c2018-07-18 13:11:31 -0700550 if (sadBytes.length == 0) {
551 mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
552 } else {
553 mService.sendCecCommand(
554 HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
555 mAddress, message.getSource(), sadBytes));
556 }
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700557 return true;
558 }
559
Nick Chalko167529c2018-07-18 13:11:31 -0700560 private byte[] getSupportedShortAudioDescriptors(
561 AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
Nick Chalkod70c4132018-08-27 16:22:53 -0700562 ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
563 for (@AudioCodec int audioFormatCode : audioFormatCodes) {
564 byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
565 if (sad != null) {
566 if (sad.length == 3) {
567
568 sads.add(sad);
569 } else {
570 HdmiLogger.warning(
571 "Dropping Short Audio Descriptor with length %d for requested codec %x",
572 sad.length, audioFormatCode);
573 }
574 }
575 }
Amyd984bc62018-11-28 18:53:54 -0800576 return getShortAudioDescriptorBytes(sads);
577 }
578
579 private byte[] getSupportedShortAudioDescriptorsFromConfig(
580 List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) {
581 DeviceConfig deviceConfigToUse = null;
582 for (DeviceConfig device : deviceConfig) {
583 // TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
584 if (device.name.equals("VX_AUDIO_DEVICE_IN_HDMI_ARC")) {
585 deviceConfigToUse = device;
586 break;
587 }
588 }
589 if (deviceConfigToUse == null) {
590 // TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
591 Slog.w(TAG, "sadConfig.xml does not have required device info for "
592 + "VX_AUDIO_DEVICE_IN_HDMI_ARC");
593 return new byte[0];
594 }
595 HashMap<Integer, byte[]> map = new HashMap<>();
596 ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
597 for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
598 map.put(codecSad.audioCodec, codecSad.sad);
599 }
600 for (int i = 0; i < audioFormatCodes.length; i++) {
601 if (map.containsKey(audioFormatCodes[i])) {
602 byte[] sad = map.get(audioFormatCodes[i]);
603 if (sad != null && sad.length == 3) {
604 sads.add(sad);
605 }
606 }
607 }
608 return getShortAudioDescriptorBytes(sads);
609 }
610
611 private byte[] getShortAudioDescriptorBytes(ArrayList<byte[]> sads) {
Nick Chalkod70c4132018-08-27 16:22:53 -0700612 // Short Audio Descriptors are always 3 bytes long.
613 byte[] bytes = new byte[sads.size() * 3];
614 int index = 0;
615 for (byte[] sad : sads) {
616 System.arraycopy(sad, 0, bytes, index, 3);
617 index += 3;
618 }
619 return bytes;
620 }
621
622 /**
623 * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
624 * audioFormatCode is not supported.
625 */
626 @Nullable
627 private byte[] getSupportedShortAudioDescriptor(
628 AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
629 switch (audioFormatCode) {
630 case Constants.AUDIO_CODEC_NONE: {
631 return null;
632 }
633 case Constants.AUDIO_CODEC_LPCM: {
634 return getLpcmShortAudioDescriptor(deviceInfo);
635 }
636 // TODO(b/80297701): implement the rest of the codecs
637 case Constants.AUDIO_CODEC_DD:
638 case Constants.AUDIO_CODEC_MPEG1:
639 case Constants.AUDIO_CODEC_MP3:
640 case Constants.AUDIO_CODEC_MPEG2:
641 case Constants.AUDIO_CODEC_AAC:
642 case Constants.AUDIO_CODEC_DTS:
643 case Constants.AUDIO_CODEC_ATRAC:
644 case Constants.AUDIO_CODEC_ONEBITAUDIO:
645 case Constants.AUDIO_CODEC_DDP:
646 case Constants.AUDIO_CODEC_DTSHD:
647 case Constants.AUDIO_CODEC_TRUEHD:
648 case Constants.AUDIO_CODEC_DST:
649 case Constants.AUDIO_CODEC_WMAPRO:
650 default: {
651 return null;
652 }
653 }
654 }
655
656 @Nullable
657 private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
658 // TODO(b/80297701): implement
659 return null;
Nick Chalko167529c2018-07-18 13:11:31 -0700660 }
661
662 @Nullable
663 private AudioDeviceInfo getSystemAudioDeviceInfo() {
Nick Chalkod70c4132018-08-27 16:22:53 -0700664 AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
665 if (audioManager == null) {
666 HdmiLogger.error(
667 "Error getting system audio device because AudioManager not available.");
668 return null;
669 }
670 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
671 HdmiLogger.debug("Found %d audio input devices", devices.length);
672 for (AudioDeviceInfo device : devices) {
673 HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
674 HdmiLogger.debug("Supported encodings are %s",
675 Arrays.stream(device.getEncodings()).mapToObj(
676 AudioFormat::toLogFriendlyEncoding
677 ).collect(Collectors.joining(", ")));
678 // TODO(b/80297701) use the actual device type that system audio mode is connected to.
679 if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
680 return device;
681 }
682 }
Nick Chalko167529c2018-07-18 13:11:31 -0700683 return null;
684 }
685
686 @AudioCodec
687 private int[] parseAudioFormatCodes(byte[] params) {
688 @AudioCodec int[] audioFormatCodes = new int[params.length];
689 for (int i = 0; i < params.length; i++) {
690 byte val = params[i];
691 audioFormatCodes[i] =
Amy06dc4cd2018-08-09 18:22:40 -0700692 val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
Nick Chalko167529c2018-07-18 13:11:31 -0700693 }
694 return audioFormatCodes;
695 }
696
Amy9b91e8c2018-06-11 17:26:26 -0700697 @Override
698 @ServiceThreadOnly
699 protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
700 assertRunOnServiceThread();
701 boolean systemAudioStatusOn = message.getParams().length != 0;
Amy06dc4cd2018-08-09 18:22:40 -0700702 // Check if the request comes from a non-TV device.
703 // Need to check if TV supports System Audio Control
704 // if non-TV device tries to turn on the feature
705 if (message.getSource() != Constants.ADDR_TV) {
706 if (systemAudioStatusOn) {
707 handleSystemAudioModeOnFromNonTvDevice(message);
708 return true;
709 }
710 } else {
711 // If TV request the feature on
712 // cache TV supporting System Audio Control
713 // until Audio System loses its physical address.
714 setTvSystemAudioModeSupport(true);
715 }
716 // If TV or Audio System does not support the feature,
717 // will send abort command.
718 if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
Amy9b91e8c2018-06-11 17:26:26 -0700719 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
720 return true;
721 }
722
Nick Chalkof32fcea2018-07-17 16:17:40 -0700723 mService.sendCecCommand(
724 HdmiCecMessageBuilder.buildSetSystemAudioMode(
725 mAddress, Constants.ADDR_BROADCAST, systemAudioStatusOn));
Amy9b91e8c2018-06-11 17:26:26 -0700726 return true;
727 }
728
729 @Override
730 @ServiceThreadOnly
731 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
732 assertRunOnServiceThread();
Amy06dc4cd2018-08-09 18:22:40 -0700733 if (!checkSupportAndSetSystemAudioMode(
734 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
Amy9b91e8c2018-06-11 17:26:26 -0700735 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
736 }
737 return true;
738 }
739
740 @Override
741 @ServiceThreadOnly
742 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
743 assertRunOnServiceThread();
Amy06dc4cd2018-08-09 18:22:40 -0700744 if (!checkSupportAndSetSystemAudioMode(
745 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
Amy9b91e8c2018-06-11 17:26:26 -0700746 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
747 }
748 return true;
749 }
750
Shubang81170da2018-07-12 18:02:52 -0700751 @ServiceThreadOnly
752 void setArcStatus(boolean enabled) {
753 // TODO(shubang): add tests
754 assertRunOnServiceThread();
755
756 HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
757 // 1. Enable/disable ARC circuit.
758 enableAudioReturnChannel(enabled);
759 // 2. Notify arc status to audio service.
760 notifyArcStatusToAudioService(enabled);
761 // 3. Update arc status;
762 mArcEstablished = enabled;
763 }
764
Nick Chalko167529c2018-07-18 13:11:31 -0700765 /** Switch hardware ARC circuit in the system. */
Shubang81170da2018-07-12 18:02:52 -0700766 @ServiceThreadOnly
767 private void enableAudioReturnChannel(boolean enabled) {
768 assertRunOnServiceThread();
769 mService.enableAudioReturnChannel(
Nick Chalko167529c2018-07-18 13:11:31 -0700770 SystemProperties.getInt(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, 0),
Shubang81170da2018-07-12 18:02:52 -0700771 enabled);
772 }
773
774 private void notifyArcStatusToAudioService(boolean enabled) {
775 // Note that we don't set any name to ARC.
Nick Chalko167529c2018-07-18 13:11:31 -0700776 mService.getAudioManager()
Amy06dc4cd2018-08-09 18:22:40 -0700777 .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
Shubang81170da2018-07-12 18:02:52 -0700778 }
779
Nick Chalko01b979c2018-10-19 14:54:30 -0700780 void reportAudioStatus(int source) {
Amy2a6c3dc2018-06-05 17:31:55 -0700781 assertRunOnServiceThread();
782
783 int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
784 boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
785 int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
Nick Chalko01b979c2018-10-19 14:54:30 -0700786 int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
Amy2a6c3dc2018-06-05 17:31:55 -0700787 int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
Nick Chalko01b979c2018-10-19 14:54:30 -0700788 HdmiLogger.debug("Reporting volume %i (%i-%i) as CEC volume %i", volume,
789 minVolume, maxVolume, scaledVolume);
Amy2a6c3dc2018-06-05 17:31:55 -0700790
Nick Chalkof32fcea2018-07-17 16:17:40 -0700791 mService.sendCecCommand(
792 HdmiCecMessageBuilder.buildReportAudioStatus(
Nick Chalko01b979c2018-10-19 14:54:30 -0700793 mAddress, source, scaledVolume, mute));
Amy2a6c3dc2018-06-05 17:31:55 -0700794 }
Amy9b91e8c2018-06-11 17:26:26 -0700795
Amy06dc4cd2018-08-09 18:22:40 -0700796 /**
797 * Method to check if device support System Audio Control. If so, wake up device if necessary.
798 *
799 * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
800 * @param newSystemAudioMode turning feature on or off. True is on. False is off.
801 * @return true or false.
802 *
803 * <p>False when device does not support the feature. Otherwise returns true.
804 */
805 protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
Amy9b91e8c2018-06-11 17:26:26 -0700806 if (!isSystemAudioControlFeatureEnabled()) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700807 HdmiLogger.debug(
808 "Cannot turn "
809 + (newSystemAudioMode ? "on" : "off")
810 + "system audio mode "
811 + "because the System Audio Control feature is disabled.");
Amy9b91e8c2018-06-11 17:26:26 -0700812 return false;
813 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700814 HdmiLogger.debug(
815 "System Audio Mode change[old:%b new:%b]",
816 mSystemAudioActivated, newSystemAudioMode);
Amyae4ee342018-10-09 16:55:14 -0700817 // Wake up device if System Audio Control is turned on
818 if (newSystemAudioMode) {
Amy0fd41e32018-06-21 13:40:27 -0700819 mService.wakeUp();
Amyac1a55c2018-07-13 16:38:23 -0700820 }
Amy06dc4cd2018-08-09 18:22:40 -0700821 setSystemAudioMode(newSystemAudioMode);
822 return true;
823 }
824
825 /**
826 * Real work to turn on or off System Audio Mode.
827 *
828 * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
829 * if trying to turn on or off the feature.
830 */
831 private void setSystemAudioMode(boolean newSystemAudioMode) {
Amyac1a55c2018-07-13 16:38:23 -0700832 int targetPhysicalAddress = getActiveSource().physicalAddress;
Amy00638112018-10-31 17:47:17 -0700833 int port = mService.pathToPortId(targetPhysicalAddress);
Amy6a6d6182018-08-20 17:38:47 -0700834 if (newSystemAudioMode && port >= 0) {
Amyac1a55c2018-07-13 16:38:23 -0700835 switchToAudioInput();
Amy0fd41e32018-06-21 13:40:27 -0700836 }
Amycce55e02018-08-20 15:42:25 -0700837 // Mute device when feature is turned off and unmute device when feature is turned on.
838 // PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE is false when device never needs to be muted.
839 boolean currentMuteStatus =
840 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
Amy61fc25f2018-11-02 17:53:21 -0700841 if (currentMuteStatus == newSystemAudioMode) {
Amy59c06c12019-01-18 15:35:15 -0800842 if (mService.readBooleanSystemProperty(
Amy61fc25f2018-11-02 17:53:21 -0700843 Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
844 || newSystemAudioMode) {
845 mService.getAudioManager()
846 .adjustStreamVolume(
847 AudioManager.STREAM_MUSIC,
848 newSystemAudioMode
849 ? AudioManager.ADJUST_UNMUTE
850 : AudioManager.ADJUST_MUTE,
851 0);
852 }
Amycce55e02018-08-20 15:42:25 -0700853 }
Amy9b91e8c2018-06-11 17:26:26 -0700854 updateAudioManagerForSystemAudio(newSystemAudioMode);
855 synchronized (mLock) {
856 if (mSystemAudioActivated != newSystemAudioMode) {
857 mSystemAudioActivated = newSystemAudioMode;
858 mService.announceSystemAudioModeChange(newSystemAudioMode);
859 }
860 }
Amyacc1dab2018-09-06 17:09:51 -0700861 // Init arc whenever System Audio Mode is on
Amy03afe482018-09-18 16:57:45 -0700862 // Terminate arc when System Audio Mode is off
863 // Since some TVs don't request ARC on with System Audio Mode on request
864 if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
865 && isDirectConnectToTv()) {
866 if (newSystemAudioMode && !isArcEnabled()) {
867 removeAction(ArcInitiationActionFromAvr.class);
868 addAndStartAction(new ArcInitiationActionFromAvr(this));
869 } else if (!newSystemAudioMode && isArcEnabled()) {
870 removeAction(ArcTerminationActionFromAvr.class);
871 addAndStartAction(new ArcTerminationActionFromAvr(this));
872 }
Amyacc1dab2018-09-06 17:09:51 -0700873 }
Amy9b91e8c2018-06-11 17:26:26 -0700874 }
875
Amyac1a55c2018-07-13 16:38:23 -0700876 protected void switchToAudioInput() {
877 // TODO(b/111396634): switch input according to PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT
878 }
879
Amy77c7c982018-07-23 18:27:36 -0700880 protected boolean isDirectConnectToTv() {
881 int myPhysicalAddress = mService.getPhysicalAddress();
882 return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress;
883 }
884
Amy9b91e8c2018-06-11 17:26:26 -0700885 private void updateAudioManagerForSystemAudio(boolean on) {
886 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
887 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
888 }
889
Amy79db52f2018-10-23 12:45:17 -0700890 void onSystemAduioControlFeatureSupportChanged(boolean enabled) {
891 setSystemAudioControlFeatureEnabled(enabled);
892 if (enabled) {
893 addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
894 }
895 }
896
Nick Chalko167529c2018-07-18 13:11:31 -0700897 @ServiceThreadOnly
898 void setSystemAudioControlFeatureEnabled(boolean enabled) {
899 assertRunOnServiceThread();
900 synchronized (mLock) {
901 mSystemAudioControlFeatureEnabled = enabled;
902 }
903 }
904
shubangd932cb52018-09-14 17:55:16 -0700905 @ServiceThreadOnly
Amy0c2e29f2018-10-23 12:17:52 -0700906 void setRoutingControlFeatureEnables(boolean enabled) {
907 assertRunOnServiceThread();
908 synchronized (mLock) {
909 mRoutingControlFeatureEnabled = enabled;
910 }
911 }
912
913 @ServiceThreadOnly
shubangd932cb52018-09-14 17:55:16 -0700914 void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
915 assertRunOnServiceThread();
916 // TODO: validate port ID
917 if (portId == getLocalActivePort()) {
918 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
919 return;
920 }
shubangd932cb52018-09-14 17:55:16 -0700921 if (!mService.isControlEnabled()) {
Amyf9fa6172018-10-11 16:47:12 -0700922 setRoutingPort(portId);
shubangd932cb52018-09-14 17:55:16 -0700923 setLocalActivePort(portId);
924 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
925 return;
926 }
Amyf9fa6172018-10-11 16:47:12 -0700927 int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
928 ? mService.portIdToPath(getRoutingPort())
shubangd932cb52018-09-14 17:55:16 -0700929 : getDeviceInfo().getPhysicalAddress();
Amyf9fa6172018-10-11 16:47:12 -0700930 int newPath = mService.portIdToPath(portId);
shubangd932cb52018-09-14 17:55:16 -0700931 if (oldPath == newPath) {
932 return;
933 }
Amyf9fa6172018-10-11 16:47:12 -0700934 setRoutingPort(portId);
shubangd932cb52018-09-14 17:55:16 -0700935 setLocalActivePort(portId);
936 HdmiCecMessage routingChange =
937 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
938 mService.sendCecCommand(routingChange);
939 }
940
Nick Chalko167529c2018-07-18 13:11:31 -0700941 boolean isSystemAudioControlFeatureEnabled() {
Amy9b91e8c2018-06-11 17:26:26 -0700942 synchronized (mLock) {
943 return mSystemAudioControlFeatureEnabled;
944 }
945 }
Nick Chalkof28c7b52018-06-14 07:46:00 -0700946
Shubang595a1832018-06-27 17:52:18 -0700947 protected boolean isSystemAudioActivated() {
948 synchronized (mLock) {
949 return mSystemAudioActivated;
950 }
951 }
952
Shubang2fd186e2018-07-18 16:53:12 -0700953 protected void terminateSystemAudioMode() {
954 // remove pending initiation actions
955 removeAction(SystemAudioInitiationActionFromAvr.class);
Shubangbc2aab32018-07-19 16:41:17 -0700956 if (!isSystemAudioActivated()) {
957 return;
Shubang2fd186e2018-07-18 16:53:12 -0700958 }
959
Amy06dc4cd2018-08-09 18:22:40 -0700960 if (checkSupportAndSetSystemAudioMode(false)) {
Shubangbc2aab32018-07-19 16:41:17 -0700961 // send <Set System Audio Mode> [“Off”]
962 mService.sendCecCommand(
963 HdmiCecMessageBuilder.buildSetSystemAudioMode(
964 mAddress, Constants.ADDR_BROADCAST, false));
Shubang2fd186e2018-07-18 16:53:12 -0700965 }
966 }
967
Nick Chalkof28c7b52018-06-14 07:46:00 -0700968 /** Reports if System Audio Mode is supported by the connected TV */
969 interface TvSystemAudioModeSupportedCallback {
970
971 /** {@code supported} is true if the TV is connected and supports System Audio Mode. */
972 void onResult(boolean supported);
Nick Chalkof28c7b52018-06-14 07:46:00 -0700973 }
974
975 /**
976 * Queries the connected TV to detect if System Audio Mode is supported by the TV.
977 *
978 * <p>This query may take up to 2 seconds to complete.
979 *
980 * <p>The result of the query may be cached until Audio device type is put in standby or loses
981 * its physical address.
982 */
Amy06dc4cd2018-08-09 18:22:40 -0700983 // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic.
Nick Chalkof28c7b52018-06-14 07:46:00 -0700984 void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
Shubangec668962018-07-13 19:03:19 -0700985 if (!mTvSystemAudioModeSupport) {
986 addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
987 } else {
988 callback.onResult(true);
989 }
990 }
991
Amy06dc4cd2018-08-09 18:22:40 -0700992 /**
993 * Handler of System Audio Mode Request on from non TV device
994 */
995 void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
996 if (!isSystemAudioControlFeatureEnabled()) {
997 HdmiLogger.debug(
998 "Cannot turn on" + "system audio mode "
999 + "because the System Audio Control feature is disabled.");
1000 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1001 return;
1002 }
Amyae4ee342018-10-09 16:55:14 -07001003 // Wake up device
1004 mService.wakeUp();
Amy06dc4cd2018-08-09 18:22:40 -07001005 // Check if TV supports System Audio Control.
1006 // Handle broadcasting setSystemAudioMode on or aborting message on callback.
1007 queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
1008 public void onResult(boolean supported) {
1009 if (supported) {
1010 setSystemAudioMode(true);
1011 mService.sendCecCommand(
1012 HdmiCecMessageBuilder.buildSetSystemAudioMode(
1013 mAddress, Constants.ADDR_BROADCAST, true));
1014 } else {
1015 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1016 }
1017 }
1018 });
1019 }
1020
Shubangec668962018-07-13 19:03:19 -07001021 void setTvSystemAudioModeSupport(boolean supported) {
1022 mTvSystemAudioModeSupport = supported;
Nick Chalkof28c7b52018-06-14 07:46:00 -07001023 }
Amy77c7c982018-07-23 18:27:36 -07001024
1025 @VisibleForTesting
1026 protected boolean isArcEnabled() {
1027 synchronized (mLock) {
1028 return mArcEstablished;
1029 }
1030 }
Amy93a97b92018-08-17 14:58:41 -07001031
Amy225d55a2018-09-06 11:03:51 -07001032 @Override
1033 protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
Amy00638112018-10-31 17:47:17 -07001034 int port = mService.pathToPortId(physicalAddress);
Amy34037422018-09-06 13:21:08 -07001035 if (isSystemAudioActivated() && port < 0) {
1036 // If system audio mode is on and the new active source is not under the current device,
1037 // Will switch to ARC input.
1038 // TODO(b/115637145): handle system aduio without ARC
Amy225d55a2018-09-06 11:03:51 -07001039 routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
Amy34037422018-09-06 13:21:08 -07001040 } else if (mIsSwitchDevice && port >= 0) {
1041 // If current device is a switch and the new active source is under it,
1042 // will switch to the corresponding active path.
1043 routeToInputFromPortId(port);
Amy225d55a2018-09-06 11:03:51 -07001044 }
1045 }
1046
Amy225d55a2018-09-06 11:03:51 -07001047 protected void routeToInputFromPortId(int portId) {
Amy0c2e29f2018-10-23 12:17:52 -07001048 if (!isRoutingControlFeatureEnabled()) {
1049 HdmiLogger.debug("Routing Control Feature is not enabled.");
1050 return;
1051 }
Amy225d55a2018-09-06 11:03:51 -07001052 if (mArcIntentUsed) {
1053 routeToTvInputFromPortId(portId);
1054 } else {
1055 // TODO(): implement input switching for devices not using TvInput.
1056 }
1057 }
1058
1059 protected void routeToTvInputFromPortId(int portId) {
1060 if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
1061 HdmiLogger.debug("Invalid port number for Tv Input switching.");
1062 return;
1063 }
Amy03afe482018-09-18 16:57:45 -07001064 // Wake up if the current device if ready to route.
Amyae4ee342018-10-09 16:55:14 -07001065 mService.wakeUp();
Amy34037422018-09-06 13:21:08 -07001066 if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
Amy225d55a2018-09-06 11:03:51 -07001067 switchToHomeTvInput();
1068 } else if (portId == Constants.CEC_SWITCH_ARC) {
1069 switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
Amy03afe482018-09-18 16:57:45 -07001070 setLocalActivePort(portId);
Amy225d55a2018-09-06 11:03:51 -07001071 return;
1072 } else {
Amy34037422018-09-06 13:21:08 -07001073 String uri = mTvInputs.get(portId);
1074 if (uri != null) {
1075 switchToTvInput(mTvInputs.get(portId));
1076 } else {
1077 HdmiLogger.debug("Port number does not match any Tv Input.");
1078 return;
1079 }
Amy225d55a2018-09-06 11:03:51 -07001080 }
1081
1082 setLocalActivePort(portId);
Amy03afe482018-09-18 16:57:45 -07001083 setRoutingPort(portId);
Amy225d55a2018-09-06 11:03:51 -07001084 }
1085
1086 // For device to switch to specific TvInput with corresponding URI.
1087 private void switchToTvInput(String uri) {
1088 mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
1089 TvContract.buildChannelUriForPassthroughInput(uri))
1090 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
1091 }
1092
1093 // For device using TvInput to switch to Home.
1094 private void switchToHomeTvInput() {
1095 Intent activityIntent = new Intent(Intent.ACTION_MAIN)
1096 .addCategory(Intent.CATEGORY_HOME)
1097 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
1098 | Intent.FLAG_ACTIVITY_SINGLE_TOP
1099 | Intent.FLAG_ACTIVITY_NEW_TASK
1100 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
1101 mService.getContext().startActivity(activityIntent);
1102 }
Amy79b54e92018-09-11 17:02:28 -07001103
1104 @Override
1105 protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
Amy00638112018-10-31 17:47:17 -07001106 int port = mService.pathToPortId(physicalAddress);
Amy79b54e92018-09-11 17:02:28 -07001107 // Routing change or information sent from switches under the current device can be ignored.
1108 if (port > 0) {
1109 return;
1110 }
1111 // When other switches route to some other devices not under the current device,
1112 // check system audio mode status and do ARC switch if needed.
1113 if (port < 0 && isSystemAudioActivated()) {
1114 handleRoutingChangeAndInformationForSystemAudio();
1115 return;
1116 }
1117 // When other switches route to the current device
1118 // and the current device is also a switch.
1119 if (port == 0) {
1120 handleRoutingChangeAndInformationForSwitch(message);
1121 }
1122 }
1123
1124 // Handle the system audio(ARC) part of the logic on receiving routing change or information.
1125 private void handleRoutingChangeAndInformationForSystemAudio() {
Amy79b54e92018-09-11 17:02:28 -07001126 // TODO(b/115637145): handle system aduio without ARC
1127 routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
1128 }
1129
1130 // Handle the routing control part of the logic on receiving routing change or information.
1131 private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
Amy03afe482018-09-18 16:57:45 -07001132 if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
Amy79b54e92018-09-11 17:02:28 -07001133 routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
Amy123ec402018-09-25 10:56:31 -07001134 mService.setAndBroadcastActiveSourceFromOneDeviceType(
1135 message.getSource(), mService.getPhysicalAddress());
Amy79b54e92018-09-11 17:02:28 -07001136 return;
1137 }
1138
Amy238f64f2018-11-30 11:27:56 -08001139 int routingInformationPath = mService.portIdToPath(getRoutingPort());
Amy79b54e92018-09-11 17:02:28 -07001140 // If current device is already the leaf of the whole HDMI system, will do nothing.
1141 if (routingInformationPath == mService.getPhysicalAddress()) {
1142 HdmiLogger.debug("Current device can't assign valid physical address"
1143 + "to devices under it any more. "
1144 + "It's physical address is " + routingInformationPath);
1145 return;
1146 }
1147 // Otherwise will switch to the current active port and broadcast routing information.
1148 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
1149 mAddress, routingInformationPath));
Amy03afe482018-09-18 16:57:45 -07001150 routeToInputFromPortId(getRoutingPort());
Amy79b54e92018-09-11 17:02:28 -07001151 }
Amy1e4a8cc2018-10-15 10:18:14 -07001152
1153 protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1154 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1155 if (info == null) {
1156 Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1157 return;
1158 }
1159
1160 if (info.getDevicePowerStatus() == newPowerStatus) {
1161 return;
1162 }
1163
1164 HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1165 // addDeviceInfo replaces old device info with new one if exists.
1166 addDeviceInfo(newInfo);
1167
1168 invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1169 }
Amyc00cd4e2018-10-16 20:21:33 -07001170
1171 @ServiceThreadOnly
1172 private void launchDeviceDiscovery() {
1173 assertRunOnServiceThread();
Amy47fe0b12018-11-06 14:45:18 -08001174 if (hasAction(DeviceDiscoveryAction.class)) {
1175 Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
1176 removeAction(DeviceDiscoveryAction.class);
1177 }
Amyc00cd4e2018-10-16 20:21:33 -07001178 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
1179 new DeviceDiscoveryCallback() {
1180 @Override
1181 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
1182 for (HdmiDeviceInfo info : deviceInfos) {
1183 addCecDevice(info);
1184 }
1185 }
1186 });
1187 addAndStartAction(action);
1188 }
1189
1190 // Clear all device info.
1191 @ServiceThreadOnly
1192 private void clearDeviceInfoList() {
1193 assertRunOnServiceThread();
1194 for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
1195 if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
1196 continue;
1197 }
1198 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1199 }
1200 mDeviceInfos.clear();
Amyec126a52018-10-30 16:51:14 -07001201 updateSafeDeviceInfoList();
Amyc00cd4e2018-10-16 20:21:33 -07001202 }
Nick Chalkob9e48e22018-10-23 06:59:39 -07001203
1204 @Override
1205 protected void dump(IndentingPrintWriter pw) {
1206 pw.println("HdmiCecLocalDeviceAudioSystem:");
1207 pw.increaseIndent();
1208 pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
Amy77e672c2018-10-31 15:55:40 -07001209 pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
Nick Chalkob9e48e22018-10-23 06:59:39 -07001210 pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1211 pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
1212 pw.println("mArcEstablished: " + mArcEstablished);
1213 pw.println("mArcIntentUsed: " + mArcIntentUsed);
Amy2e4b25c2018-10-30 18:08:49 -07001214 pw.println("mRoutingPort: " + getRoutingPort());
1215 pw.println("mLocalActivePort: " + getLocalActivePort());
Nick Chalkob9e48e22018-10-23 06:59:39 -07001216 HdmiUtils.dumpMap(pw, "mTvInputs:", mTvInputs);
1217 HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
1218 pw.decreaseIndent();
1219 super.dump(pw);
1220 }
1221
Nick Chalkob89d6ff2018-05-25 10:29:01 -07001222}