blob: 1ba0c52ce87586d5c2c2867fd8c66142e920e922 [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;
Amy4aef3b402019-01-18 14:17:55 -080023import android.content.ActivityNotFoundException;
Amy225d55a2018-09-06 11:03:51 -070024import android.content.Intent;
shubangd932cb52018-09-14 17:55:16 -070025import android.hardware.hdmi.HdmiControlManager;
Nick Chalkob89d6ff2018-05-25 10:29:01 -070026import android.hardware.hdmi.HdmiDeviceInfo;
Amy59176da2018-10-12 16:30:54 -070027import android.hardware.hdmi.HdmiPortInfo;
shubangd932cb52018-09-14 17:55:16 -070028import android.hardware.hdmi.IHdmiControlCallback;
Nick Chalko167529c2018-07-18 13:11:31 -070029import android.media.AudioDeviceInfo;
Nick Chalkod70c4132018-08-27 16:22:53 -070030import android.media.AudioFormat;
Amy2a6c3dc2018-06-05 17:31:55 -070031import android.media.AudioManager;
Shubang81170da2018-07-12 18:02:52 -070032import android.media.AudioSystem;
Amy225d55a2018-09-06 11:03:51 -070033import android.media.tv.TvContract;
Nick Chalkob89d6ff2018-05-25 10:29:01 -070034import android.os.SystemProperties;
Amy0c2e29f2018-10-23 12:17:52 -070035import android.provider.Settings.Global;
Amy89e93af2018-10-15 09:56:54 -070036import android.util.Slog;
37import android.util.SparseArray;
Nick Chalko167529c2018-07-18 13:11:31 -070038
Amy87eda822018-06-06 17:56:39 -070039import com.android.internal.annotations.GuardedBy;
Amy6506bd62018-07-02 17:29:36 -070040import com.android.internal.annotations.VisibleForTesting;
Nick Chalkob9e48e22018-10-23 06:59:39 -070041import com.android.internal.util.IndentingPrintWriter;
Nick Chalko167529c2018-07-18 13:11:31 -070042import com.android.server.hdmi.Constants.AudioCodec;
Amyc00cd4e2018-10-16 20:21:33 -070043import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
Amy4a922662018-06-05 17:31:55 -070044import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Amyd984bc62018-11-28 18:53:54 -080045import com.android.server.hdmi.HdmiUtils.CodecSad;
46import com.android.server.hdmi.HdmiUtils.DeviceConfig;
Nick Chalkob89d6ff2018-05-25 10:29:01 -070047
Amyd984bc62018-11-28 18:53:54 -080048import org.xmlpull.v1.XmlPullParserException;
49
50import java.io.File;
51import java.io.FileInputStream;
52import java.io.IOException;
53import java.io.InputStream;
Amy89e93af2018-10-15 09:56:54 -070054import java.io.UnsupportedEncodingException;
Nick Chalkod70c4132018-08-27 16:22:53 -070055import java.util.ArrayList;
56import java.util.Arrays;
Amyec126a52018-10-30 16:51:14 -070057import java.util.Collections;
Amy34037422018-09-06 13:21:08 -070058import java.util.HashMap;
Amyc00cd4e2018-10-16 20:21:33 -070059import java.util.List;
Nick Chalkod70c4132018-08-27 16:22:53 -070060import java.util.stream.Collectors;
61
Amy34037422018-09-06 13:21:08 -070062
Nick Chalkob89d6ff2018-05-25 10:29:01 -070063/**
Nick Chalkof32fcea2018-07-17 16:17:40 -070064 * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
65 * system.
Nick Chalkob89d6ff2018-05-25 10:29:01 -070066 */
Amy848a9f22018-08-27 17:21:26 -070067public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
Nick Chalkob89d6ff2018-05-25 10:29:01 -070068
Amy4a922662018-06-05 17:31:55 -070069 private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
70
Amy87eda822018-06-06 17:56:39 -070071 // Whether System audio mode is activated or not.
72 // This becomes true only when all system audio sequences are finished.
73 @GuardedBy("mLock")
74 private boolean mSystemAudioActivated;
75
Amy9b91e8c2018-06-11 17:26:26 -070076 // Whether the System Audio Control feature is enabled or not. True by default.
77 @GuardedBy("mLock")
78 private boolean mSystemAudioControlFeatureEnabled;
Nick Chalkof32fcea2018-07-17 16:17:40 -070079
Shubangec668962018-07-13 19:03:19 -070080 private boolean mTvSystemAudioModeSupport;
81
Shubang81170da2018-07-12 18:02:52 -070082 // Whether ARC is available or not. "true" means that ARC is established between TV and
83 // AVR as audio receiver.
Nick Chalko167529c2018-07-18 13:11:31 -070084 @ServiceThreadOnly private boolean mArcEstablished = false;
Shubang81170da2018-07-12 18:02:52 -070085
Amy34037422018-09-06 13:21:08 -070086 // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
87 // when ARC is using TvInput.
Amy225d55a2018-09-06 11:03:51 -070088 private boolean mArcIntentUsed = SystemProperties
89 .get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
Amy6a6d6182018-08-20 17:38:47 -070090
Amy34037422018-09-06 13:21:08 -070091 // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
92 // accept input switching request from HDMI devices. Requests for which the corresponding
93 // input ID is not yet registered by TV input framework need to be buffered for delayed
94 // processing.
95 private final HashMap<Integer, String> mTvInputs = new HashMap<>();
96
Amyec126a52018-10-30 16:51:14 -070097 // Copy of mDeviceInfos to guarantee thread-safety.
98 @GuardedBy("mLock")
99 private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
100
Amy5c6026b2018-10-12 14:42:43 -0700101 // Map-like container of all cec devices.
102 // device id is used as key of container.
103 private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
104
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700105 protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
106 super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
Amy0c2e29f2018-10-23 12:17:52 -0700107 mRoutingControlFeatureEnabled =
Amy77e672c2018-10-31 15:55:40 -0700108 mService.readBooleanSetting(Global.HDMI_CEC_SWITCH_ENABLED, false);
Amy79db52f2018-10-23 12:45:17 -0700109 mSystemAudioControlFeatureEnabled =
110 mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
Amy00638112018-10-31 17:47:17 -0700111 // TODO(amyjojo): Maintain a portId to TvinputId map.
112 mTvInputs.put(2, "com.droidlogic.tvinput/.services.Hdmi1InputService/HW5");
113 mTvInputs.put(4, "com.droidlogic.tvinput/.services.Hdmi2InputService/HW6");
114 mTvInputs.put(1, "com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700115 }
116
Amyd984bc62018-11-28 18:53:54 -0800117 private static final String SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH = "/vendor/etc/sadConfig.xml";
118
Amy5c6026b2018-10-12 14:42:43 -0700119 /**
120 * Called when a device is newly added or a new device is detected or
Amy1e4a8cc2018-10-15 10:18:14 -0700121 * an existing device is updated.
Amy5c6026b2018-10-12 14:42:43 -0700122 *
123 * @param info device info of a new device.
124 */
125 @ServiceThreadOnly
126 final void addCecDevice(HdmiDeviceInfo info) {
127 assertRunOnServiceThread();
128 HdmiDeviceInfo old = addDeviceInfo(info);
Amy89e93af2018-10-15 09:56:54 -0700129 if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
Amy5c6026b2018-10-12 14:42:43 -0700130 // The addition of the device itself should not be notified.
Amy89e93af2018-10-15 09:56:54 -0700131 // Note that different logical address could still be the same local device.
Amy5c6026b2018-10-12 14:42:43 -0700132 return;
133 }
134 if (old == null) {
135 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
136 } else if (!old.equals(info)) {
137 invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
138 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
139 }
140 }
141
142 /**
143 * Called when a device is removed or removal of device is detected.
144 *
145 * @param address a logical address of a device to be removed
146 */
147 @ServiceThreadOnly
148 final void removeCecDevice(int address) {
149 assertRunOnServiceThread();
150 HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
151
152 mCecMessageCache.flushMessagesFrom(address);
153 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
154 }
155
156 /**
Amy89e93af2018-10-15 09:56:54 -0700157 * Called when a device is updated.
158 *
159 * @param info device info of the updating device.
160 */
161 @ServiceThreadOnly
162 final void updateCecDevice(HdmiDeviceInfo info) {
163 assertRunOnServiceThread();
164 HdmiDeviceInfo old = addDeviceInfo(info);
165
166 if (old == null) {
167 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
168 } else if (!old.equals(info)) {
169 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
170 }
171 }
172
173 /**
174 * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
Amy5c6026b2018-10-12 14:42:43 -0700175 * logical address as new device info's.
176 *
177 * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
178 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
179 * that has the same logical address as new one has.
180 */
181 @ServiceThreadOnly
Amy89e93af2018-10-15 09:56:54 -0700182 @VisibleForTesting
183 protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
Amy5c6026b2018-10-12 14:42:43 -0700184 assertRunOnServiceThread();
185 HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
186 if (oldDeviceInfo != null) {
187 removeDeviceInfo(deviceInfo.getId());
188 }
189 mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
Amyec126a52018-10-30 16:51:14 -0700190 updateSafeDeviceInfoList();
Amy5c6026b2018-10-12 14:42:43 -0700191 return oldDeviceInfo;
192 }
193
194 /**
195 * Remove a device info corresponding to the given {@code logicalAddress}.
196 * It returns removed {@link HdmiDeviceInfo} if exists.
197 *
198 * @param id id of device to be removed
199 * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
200 */
201 @ServiceThreadOnly
202 private HdmiDeviceInfo removeDeviceInfo(int id) {
203 assertRunOnServiceThread();
204 HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
205 if (deviceInfo != null) {
206 mDeviceInfos.remove(id);
207 }
Amyec126a52018-10-30 16:51:14 -0700208 updateSafeDeviceInfoList();
Amy5c6026b2018-10-12 14:42:43 -0700209 return deviceInfo;
210 }
211
212 /**
213 * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
214 *
215 * @param logicalAddress logical address of the device to be retrieved
216 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
217 * Returns null if no logical address matched
218 */
219 @ServiceThreadOnly
220 HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
221 assertRunOnServiceThread();
222 return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
223 }
224
Amyec126a52018-10-30 16:51:14 -0700225 @ServiceThreadOnly
226 private void updateSafeDeviceInfoList() {
227 assertRunOnServiceThread();
228 List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
229 synchronized (mLock) {
230 mSafeAllDeviceInfos = copiedDevices;
231 }
232 }
233
234 @GuardedBy("mLock")
235 List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
236 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
237 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
238 infoList.add(info);
239 }
240 return infoList;
241 }
242
Amy5c6026b2018-10-12 14:42:43 -0700243 private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
244 mService.invokeDeviceEventListeners(info, status);
245 }
246
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700247 @Override
Amy4a922662018-06-05 17:31:55 -0700248 @ServiceThreadOnly
Amy59176da2018-10-12 16:30:54 -0700249 void onHotplug(int portId, boolean connected) {
250 assertRunOnServiceThread();
251 if (connected) {
252 mService.wakeUp();
253 }
254 if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
255 mCecMessageCache.flushAll();
256 } else {
257 if (connected) {
Amyc00cd4e2018-10-16 20:21:33 -0700258 launchDeviceDiscovery();
Amy59176da2018-10-12 16:30:54 -0700259 } else {
260 // TODO(amyjojo): remove device from mDeviceInfo
261 }
262 }
263 }
264
265 @Override
266 @ServiceThreadOnly
Amyb887fa02018-06-21 11:22:13 -0700267 protected void onStandby(boolean initiatedByCec, int standbyAction) {
268 assertRunOnServiceThread();
Shubangec668962018-07-13 19:03:19 -0700269 mTvSystemAudioModeSupport = false;
Amy6506bd62018-07-02 17:29:36 -0700270 // Record the last state of System Audio Control before going to standby
271 synchronized (mLock) {
Amy59c06c12019-01-18 15:35:15 -0800272 mService.writeStringSystemProperty(
Nick Chalkof32fcea2018-07-17 16:17:40 -0700273 Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
274 mSystemAudioActivated ? "true" : "false");
Amy6506bd62018-07-02 17:29:36 -0700275 }
Shubangbc2aab32018-07-19 16:41:17 -0700276 terminateSystemAudioMode();
Amyb887fa02018-06-21 11:22:13 -0700277 }
278
279 @Override
280 @ServiceThreadOnly
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700281 protected void onAddressAllocated(int logicalAddress, int reason) {
282 assertRunOnServiceThread();
Amyb9d7f432018-11-30 15:08:30 -0800283 if (reason == mService.INITIATED_BY_ENABLE_CEC) {
284 mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
285 getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
286 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700287 mService.sendCecCommand(
288 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
289 mAddress, mService.getPhysicalAddress(), mDeviceType));
290 mService.sendCecCommand(
291 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, mService.getVendorId()));
292 int systemAudioControlOnPowerOnProp =
293 SystemProperties.getInt(
294 PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
295 ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
296 boolean lastSystemAudioControlStatus =
297 SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
Amy6506bd62018-07-02 17:29:36 -0700298 systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
Amyc00cd4e2018-10-16 20:21:33 -0700299 clearDeviceInfoList();
300 launchDeviceDiscovery();
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700301 startQueuedActions();
302 }
303
Shubang Lu00b976a2018-08-01 18:11:46 -0700304 @Override
305 protected int findKeyReceiverAddress() {
Amy2a5e2452018-11-29 20:32:20 -0800306 if (getActiveSource().isValid()) {
307 return getActiveSource().logicalAddress;
308 }
309 return Constants.ADDR_INVALID;
Shubang Lu00b976a2018-08-01 18:11:46 -0700310 }
311
Amy6506bd62018-07-02 17:29:36 -0700312 @VisibleForTesting
313 protected void systemAudioControlOnPowerOn(
Nick Chalkof32fcea2018-07-17 16:17:40 -0700314 int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
315 if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
316 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
Amy79db52f2018-10-23 12:45:17 -0700317 && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
Amy6506bd62018-07-02 17:29:36 -0700318 addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
319 }
320 }
321
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700322 @Override
Amy4a922662018-06-05 17:31:55 -0700323 @ServiceThreadOnly
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700324 protected int getPreferredAddress() {
325 assertRunOnServiceThread();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700326 return SystemProperties.getInt(
Amy06dc4cd2018-08-09 18:22:40 -0700327 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700328 }
329
330 @Override
Amy4a922662018-06-05 17:31:55 -0700331 @ServiceThreadOnly
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700332 protected void setPreferredAddress(int addr) {
333 assertRunOnServiceThread();
Amy59c06c12019-01-18 15:35:15 -0800334 mService.writeStringSystemProperty(
Nick Chalkof32fcea2018-07-17 16:17:40 -0700335 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
Nick Chalkob89d6ff2018-05-25 10:29:01 -0700336 }
Amy4a922662018-06-05 17:31:55 -0700337
338 @Override
339 @ServiceThreadOnly
Amy89e93af2018-10-15 09:56:54 -0700340 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
341 assertRunOnServiceThread();
342 int path = HdmiUtils.twoBytesToInt(message.getParams());
343 int address = message.getSource();
344 int type = message.getParams()[2];
345
346 // Ignore if [Device Discovery Action] is going on.
347 if (hasAction(DeviceDiscoveryAction.class)) {
348 Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
349 return true;
350 }
351
352 // Update the device info with TIF, note that the same device info could have added in
353 // device discovery and we do not want to override it with default OSD name. Therefore we
354 // need the following check to skip redundant device info updating.
355 HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
356 if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
357 addCecDevice(new HdmiDeviceInfo(
358 address, path, mService.pathToPortId(path), type,
359 Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
360 // if we are adding a new device info, send out a give osd name command
361 // to update the name of the device in TIF
362 mService.sendCecCommand(
363 HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
364 return true;
365 }
366
367 Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
368 return true;
369 }
370
371 @Override
Amy1e4a8cc2018-10-15 10:18:14 -0700372 protected boolean handleReportPowerStatus(HdmiCecMessage command) {
373 int newStatus = command.getParams()[0] & 0xFF;
374 updateDevicePowerStatus(command.getSource(), newStatus);
375 return true;
376 }
377
378 @Override
Amy89e93af2018-10-15 09:56:54 -0700379 @ServiceThreadOnly
380 protected boolean handleSetOsdName(HdmiCecMessage message) {
381 int source = message.getSource();
382 String osdName;
383 HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
384 // If the device is not in device list, ignore it.
385 if (deviceInfo == null) {
386 Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
387 return true;
388 }
389 try {
390 osdName = new String(message.getParams(), "US-ASCII");
391 } catch (UnsupportedEncodingException e) {
392 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
393 return true;
394 }
395
396 if (deviceInfo.getDisplayName().equals(osdName)) {
397 Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
398 return true;
399 }
400
401 Slog.d(TAG, "Updating device OSD name from "
402 + deviceInfo.getDisplayName()
403 + " to " + osdName);
404 updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
405 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
406 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
407 return true;
408 }
409
410 @Override
411 @ServiceThreadOnly
Amy4a922662018-06-05 17:31:55 -0700412 protected boolean handleReportAudioStatus(HdmiCecMessage message) {
413 assertRunOnServiceThread();
414 // TODO(amyjojo): implement report audio status handler
415 HdmiLogger.debug(TAG + "Stub handleReportAudioStatus");
416 return true;
417 }
418
419 @Override
420 @ServiceThreadOnly
421 protected boolean handleInitiateArc(HdmiCecMessage message) {
422 assertRunOnServiceThread();
423 // TODO(amyjojo): implement initiate arc handler
424 HdmiLogger.debug(TAG + "Stub handleInitiateArc");
425 return true;
426 }
427
428 @Override
429 @ServiceThreadOnly
430 protected boolean handleReportArcInitiate(HdmiCecMessage message) {
431 assertRunOnServiceThread();
432 // TODO(amyjojo): implement report arc initiate handler
433 HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
434 return true;
435 }
436
437 @Override
438 @ServiceThreadOnly
439 protected boolean handleReportArcTermination(HdmiCecMessage message) {
440 assertRunOnServiceThread();
441 // TODO(amyjojo): implement report arc terminate handler
442 HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
443 return true;
444 }
Amy2a6c3dc2018-06-05 17:31:55 -0700445
446 @Override
447 @ServiceThreadOnly
448 protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
449 assertRunOnServiceThread();
Amy79db52f2018-10-23 12:45:17 -0700450 if (isSystemAudioControlFeatureEnabled()) {
451 reportAudioStatus(message.getSource());
452 } else {
453 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
454 }
Amy2a6c3dc2018-06-05 17:31:55 -0700455 return true;
456 }
457
Amy87eda822018-06-06 17:56:39 -0700458 @Override
459 @ServiceThreadOnly
460 protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
461 assertRunOnServiceThread();
Amy27ae4032019-01-31 16:42:16 -0800462 // If the audio system is initiating the system audio mode on and TV asks the sam status at
463 // the same time, respond with true. Since we know TV supports sam in this situation.
464 // If the query comes from STB, we should respond with the current sam status and the STB
465 // should listen to the <Set System Audio Mode> broadcasting.
466 boolean isSystemAudioModeOnOrTurningOn = isSystemAudioActivated();
467 if (!isSystemAudioModeOnOrTurningOn
468 && message.getSource() == Constants.ADDR_TV
469 && hasAction(SystemAudioInitiationActionFromAvr.class)) {
470 isSystemAudioModeOnOrTurningOn = true;
471 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700472 mService.sendCecCommand(
473 HdmiCecMessageBuilder.buildReportSystemAudioMode(
Amy27ae4032019-01-31 16:42:16 -0800474 mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn));
Amy87eda822018-06-06 17:56:39 -0700475 return true;
476 }
477
Amy4e7ff1a2018-06-07 16:24:31 -0700478 @Override
479 @ServiceThreadOnly
480 protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
481 assertRunOnServiceThread();
Amy03afe482018-09-18 16:57:45 -0700482 removeAction(ArcInitiationActionFromAvr.class);
Amy59c06c12019-01-18 15:35:15 -0800483 if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
Amy77c7c982018-07-23 18:27:36 -0700484 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
485 } else if (!isDirectConnectToTv()) {
486 HdmiLogger.debug("AVR device is not directly connected with TV");
487 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
488 } else {
489 addAndStartAction(new ArcInitiationActionFromAvr(this));
490 }
Amy4e7ff1a2018-06-07 16:24:31 -0700491 return true;
492 }
493
494 @Override
495 @ServiceThreadOnly
496 protected boolean handleRequestArcTermination(HdmiCecMessage message) {
497 assertRunOnServiceThread();
Amy77c7c982018-07-23 18:27:36 -0700498 if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
499 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
500 } else if (!isArcEnabled()) {
501 HdmiLogger.debug("ARC is not established between TV and AVR device");
502 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
503 } else {
Amy03afe482018-09-18 16:57:45 -0700504 removeAction(ArcTerminationActionFromAvr.class);
Amy77c7c982018-07-23 18:27:36 -0700505 addAndStartAction(new ArcTerminationActionFromAvr(this));
506 }
Amy4e7ff1a2018-06-07 16:24:31 -0700507 return true;
508 }
509
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700510 @ServiceThreadOnly
511 protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
512 assertRunOnServiceThread();
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700513 HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
Nick Chalko167529c2018-07-18 13:11:31 -0700514 if (!isSystemAudioControlFeatureEnabled()) {
515 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
516 return true;
517 }
518 if (!isSystemAudioActivated()) {
519 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
520 return true;
521 }
Amyd984bc62018-11-28 18:53:54 -0800522
523 List<DeviceConfig> config = null;
524 File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH);
525 if (file.exists()) {
526 try {
527 InputStream in = new FileInputStream(file);
528 config = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in);
529 in.close();
530 } catch (IOException e) {
531 Slog.e(TAG, "Error reading file: " + file, e);
532 } catch (XmlPullParserException e) {
533 Slog.e(TAG, "Unable to parse file: " + file, e);
534 }
Nick Chalko167529c2018-07-18 13:11:31 -0700535 }
Amyd984bc62018-11-28 18:53:54 -0800536
Nick Chalko167529c2018-07-18 13:11:31 -0700537 @AudioCodec int[] audioFormatCodes = parseAudioFormatCodes(message.getParams());
Amyd984bc62018-11-28 18:53:54 -0800538 byte[] sadBytes;
539 if (config != null && config.size() > 0) {
540 sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioFormatCodes);
541 } else {
542 AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
543 if (deviceInfo == null) {
544 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
545 return true;
546 }
547
548 sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes);
549 }
550
Nick Chalko167529c2018-07-18 13:11:31 -0700551 if (sadBytes.length == 0) {
552 mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
553 } else {
554 mService.sendCecCommand(
555 HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
556 mAddress, message.getSource(), sadBytes));
557 }
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700558 return true;
559 }
560
Nick Chalko167529c2018-07-18 13:11:31 -0700561 private byte[] getSupportedShortAudioDescriptors(
562 AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
Nick Chalkod70c4132018-08-27 16:22:53 -0700563 ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
564 for (@AudioCodec int audioFormatCode : audioFormatCodes) {
565 byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
566 if (sad != null) {
567 if (sad.length == 3) {
568
569 sads.add(sad);
570 } else {
571 HdmiLogger.warning(
572 "Dropping Short Audio Descriptor with length %d for requested codec %x",
573 sad.length, audioFormatCode);
574 }
575 }
576 }
Amyd984bc62018-11-28 18:53:54 -0800577 return getShortAudioDescriptorBytes(sads);
578 }
579
580 private byte[] getSupportedShortAudioDescriptorsFromConfig(
581 List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) {
582 DeviceConfig deviceConfigToUse = null;
583 for (DeviceConfig device : deviceConfig) {
584 // TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
585 if (device.name.equals("VX_AUDIO_DEVICE_IN_HDMI_ARC")) {
586 deviceConfigToUse = device;
587 break;
588 }
589 }
590 if (deviceConfigToUse == null) {
591 // TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
592 Slog.w(TAG, "sadConfig.xml does not have required device info for "
593 + "VX_AUDIO_DEVICE_IN_HDMI_ARC");
594 return new byte[0];
595 }
596 HashMap<Integer, byte[]> map = new HashMap<>();
597 ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
598 for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
599 map.put(codecSad.audioCodec, codecSad.sad);
600 }
601 for (int i = 0; i < audioFormatCodes.length; i++) {
602 if (map.containsKey(audioFormatCodes[i])) {
603 byte[] sad = map.get(audioFormatCodes[i]);
604 if (sad != null && sad.length == 3) {
605 sads.add(sad);
606 }
607 }
608 }
609 return getShortAudioDescriptorBytes(sads);
610 }
611
612 private byte[] getShortAudioDescriptorBytes(ArrayList<byte[]> sads) {
Nick Chalkod70c4132018-08-27 16:22:53 -0700613 // Short Audio Descriptors are always 3 bytes long.
614 byte[] bytes = new byte[sads.size() * 3];
615 int index = 0;
616 for (byte[] sad : sads) {
617 System.arraycopy(sad, 0, bytes, index, 3);
618 index += 3;
619 }
620 return bytes;
621 }
622
623 /**
624 * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
625 * audioFormatCode is not supported.
626 */
627 @Nullable
628 private byte[] getSupportedShortAudioDescriptor(
629 AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
630 switch (audioFormatCode) {
631 case Constants.AUDIO_CODEC_NONE: {
632 return null;
633 }
634 case Constants.AUDIO_CODEC_LPCM: {
635 return getLpcmShortAudioDescriptor(deviceInfo);
636 }
637 // TODO(b/80297701): implement the rest of the codecs
638 case Constants.AUDIO_CODEC_DD:
639 case Constants.AUDIO_CODEC_MPEG1:
640 case Constants.AUDIO_CODEC_MP3:
641 case Constants.AUDIO_CODEC_MPEG2:
642 case Constants.AUDIO_CODEC_AAC:
643 case Constants.AUDIO_CODEC_DTS:
644 case Constants.AUDIO_CODEC_ATRAC:
645 case Constants.AUDIO_CODEC_ONEBITAUDIO:
646 case Constants.AUDIO_CODEC_DDP:
647 case Constants.AUDIO_CODEC_DTSHD:
648 case Constants.AUDIO_CODEC_TRUEHD:
649 case Constants.AUDIO_CODEC_DST:
650 case Constants.AUDIO_CODEC_WMAPRO:
651 default: {
652 return null;
653 }
654 }
655 }
656
657 @Nullable
658 private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
659 // TODO(b/80297701): implement
660 return null;
Nick Chalko167529c2018-07-18 13:11:31 -0700661 }
662
663 @Nullable
664 private AudioDeviceInfo getSystemAudioDeviceInfo() {
Nick Chalkod70c4132018-08-27 16:22:53 -0700665 AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
666 if (audioManager == null) {
667 HdmiLogger.error(
668 "Error getting system audio device because AudioManager not available.");
669 return null;
670 }
671 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
672 HdmiLogger.debug("Found %d audio input devices", devices.length);
673 for (AudioDeviceInfo device : devices) {
674 HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
675 HdmiLogger.debug("Supported encodings are %s",
676 Arrays.stream(device.getEncodings()).mapToObj(
677 AudioFormat::toLogFriendlyEncoding
678 ).collect(Collectors.joining(", ")));
679 // TODO(b/80297701) use the actual device type that system audio mode is connected to.
680 if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
681 return device;
682 }
683 }
Nick Chalko167529c2018-07-18 13:11:31 -0700684 return null;
685 }
686
687 @AudioCodec
688 private int[] parseAudioFormatCodes(byte[] params) {
689 @AudioCodec int[] audioFormatCodes = new int[params.length];
690 for (int i = 0; i < params.length; i++) {
691 byte val = params[i];
692 audioFormatCodes[i] =
Amy06dc4cd2018-08-09 18:22:40 -0700693 val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
Nick Chalko167529c2018-07-18 13:11:31 -0700694 }
695 return audioFormatCodes;
696 }
697
Amy9b91e8c2018-06-11 17:26:26 -0700698 @Override
699 @ServiceThreadOnly
700 protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
701 assertRunOnServiceThread();
702 boolean systemAudioStatusOn = message.getParams().length != 0;
Amy06dc4cd2018-08-09 18:22:40 -0700703 // Check if the request comes from a non-TV device.
704 // Need to check if TV supports System Audio Control
705 // if non-TV device tries to turn on the feature
706 if (message.getSource() != Constants.ADDR_TV) {
707 if (systemAudioStatusOn) {
708 handleSystemAudioModeOnFromNonTvDevice(message);
709 return true;
710 }
711 } else {
712 // If TV request the feature on
713 // cache TV supporting System Audio Control
714 // until Audio System loses its physical address.
715 setTvSystemAudioModeSupport(true);
716 }
717 // If TV or Audio System does not support the feature,
718 // will send abort command.
719 if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
Amy9b91e8c2018-06-11 17:26:26 -0700720 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
721 return true;
722 }
723
Nick Chalkof32fcea2018-07-17 16:17:40 -0700724 mService.sendCecCommand(
725 HdmiCecMessageBuilder.buildSetSystemAudioMode(
726 mAddress, Constants.ADDR_BROADCAST, systemAudioStatusOn));
Amy9b91e8c2018-06-11 17:26:26 -0700727 return true;
728 }
729
730 @Override
731 @ServiceThreadOnly
732 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
733 assertRunOnServiceThread();
Amy06dc4cd2018-08-09 18:22:40 -0700734 if (!checkSupportAndSetSystemAudioMode(
735 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
Amy9b91e8c2018-06-11 17:26:26 -0700736 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
737 }
738 return true;
739 }
740
741 @Override
742 @ServiceThreadOnly
743 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
744 assertRunOnServiceThread();
Amy06dc4cd2018-08-09 18:22:40 -0700745 if (!checkSupportAndSetSystemAudioMode(
746 HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
Amy9b91e8c2018-06-11 17:26:26 -0700747 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
748 }
749 return true;
750 }
751
Shubang81170da2018-07-12 18:02:52 -0700752 @ServiceThreadOnly
753 void setArcStatus(boolean enabled) {
754 // TODO(shubang): add tests
755 assertRunOnServiceThread();
756
757 HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
758 // 1. Enable/disable ARC circuit.
759 enableAudioReturnChannel(enabled);
760 // 2. Notify arc status to audio service.
761 notifyArcStatusToAudioService(enabled);
762 // 3. Update arc status;
763 mArcEstablished = enabled;
764 }
765
Nick Chalko167529c2018-07-18 13:11:31 -0700766 /** Switch hardware ARC circuit in the system. */
Shubang81170da2018-07-12 18:02:52 -0700767 @ServiceThreadOnly
768 private void enableAudioReturnChannel(boolean enabled) {
769 assertRunOnServiceThread();
770 mService.enableAudioReturnChannel(
Nick Chalko167529c2018-07-18 13:11:31 -0700771 SystemProperties.getInt(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, 0),
Shubang81170da2018-07-12 18:02:52 -0700772 enabled);
773 }
774
775 private void notifyArcStatusToAudioService(boolean enabled) {
776 // Note that we don't set any name to ARC.
Nick Chalko167529c2018-07-18 13:11:31 -0700777 mService.getAudioManager()
Amy06dc4cd2018-08-09 18:22:40 -0700778 .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
Shubang81170da2018-07-12 18:02:52 -0700779 }
780
Nick Chalko01b979c2018-10-19 14:54:30 -0700781 void reportAudioStatus(int source) {
Amy2a6c3dc2018-06-05 17:31:55 -0700782 assertRunOnServiceThread();
783
784 int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
785 boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
786 int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
Nick Chalko01b979c2018-10-19 14:54:30 -0700787 int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
Amy2a6c3dc2018-06-05 17:31:55 -0700788 int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
Nick Chalko01b979c2018-10-19 14:54:30 -0700789 HdmiLogger.debug("Reporting volume %i (%i-%i) as CEC volume %i", volume,
790 minVolume, maxVolume, scaledVolume);
Amy2a6c3dc2018-06-05 17:31:55 -0700791
Nick Chalkof32fcea2018-07-17 16:17:40 -0700792 mService.sendCecCommand(
793 HdmiCecMessageBuilder.buildReportAudioStatus(
Nick Chalko01b979c2018-10-19 14:54:30 -0700794 mAddress, source, scaledVolume, mute));
Amy2a6c3dc2018-06-05 17:31:55 -0700795 }
Amy9b91e8c2018-06-11 17:26:26 -0700796
Amy06dc4cd2018-08-09 18:22:40 -0700797 /**
798 * Method to check if device support System Audio Control. If so, wake up device if necessary.
799 *
800 * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
801 * @param newSystemAudioMode turning feature on or off. True is on. False is off.
802 * @return true or false.
803 *
804 * <p>False when device does not support the feature. Otherwise returns true.
805 */
806 protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
Amy9b91e8c2018-06-11 17:26:26 -0700807 if (!isSystemAudioControlFeatureEnabled()) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700808 HdmiLogger.debug(
809 "Cannot turn "
810 + (newSystemAudioMode ? "on" : "off")
811 + "system audio mode "
812 + "because the System Audio Control feature is disabled.");
Amy9b91e8c2018-06-11 17:26:26 -0700813 return false;
814 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700815 HdmiLogger.debug(
816 "System Audio Mode change[old:%b new:%b]",
817 mSystemAudioActivated, newSystemAudioMode);
Amyae4ee342018-10-09 16:55:14 -0700818 // Wake up device if System Audio Control is turned on
819 if (newSystemAudioMode) {
Amy0fd41e32018-06-21 13:40:27 -0700820 mService.wakeUp();
Amyac1a55c2018-07-13 16:38:23 -0700821 }
Amy06dc4cd2018-08-09 18:22:40 -0700822 setSystemAudioMode(newSystemAudioMode);
823 return true;
824 }
825
826 /**
827 * Real work to turn on or off System Audio Mode.
828 *
829 * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
830 * if trying to turn on or off the feature.
831 */
832 private void setSystemAudioMode(boolean newSystemAudioMode) {
Amyac1a55c2018-07-13 16:38:23 -0700833 int targetPhysicalAddress = getActiveSource().physicalAddress;
Amy00638112018-10-31 17:47:17 -0700834 int port = mService.pathToPortId(targetPhysicalAddress);
Amy6a6d6182018-08-20 17:38:47 -0700835 if (newSystemAudioMode && port >= 0) {
Amyac1a55c2018-07-13 16:38:23 -0700836 switchToAudioInput();
Amy0fd41e32018-06-21 13:40:27 -0700837 }
Amycce55e02018-08-20 15:42:25 -0700838 // Mute device when feature is turned off and unmute device when feature is turned on.
839 // PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE is false when device never needs to be muted.
840 boolean currentMuteStatus =
841 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
Amy61fc25f2018-11-02 17:53:21 -0700842 if (currentMuteStatus == newSystemAudioMode) {
Amy59c06c12019-01-18 15:35:15 -0800843 if (mService.readBooleanSystemProperty(
Amy61fc25f2018-11-02 17:53:21 -0700844 Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
845 || newSystemAudioMode) {
846 mService.getAudioManager()
847 .adjustStreamVolume(
848 AudioManager.STREAM_MUSIC,
849 newSystemAudioMode
850 ? AudioManager.ADJUST_UNMUTE
851 : AudioManager.ADJUST_MUTE,
852 0);
853 }
Amycce55e02018-08-20 15:42:25 -0700854 }
Amy9b91e8c2018-06-11 17:26:26 -0700855 updateAudioManagerForSystemAudio(newSystemAudioMode);
856 synchronized (mLock) {
857 if (mSystemAudioActivated != newSystemAudioMode) {
858 mSystemAudioActivated = newSystemAudioMode;
859 mService.announceSystemAudioModeChange(newSystemAudioMode);
860 }
861 }
Amyacc1dab2018-09-06 17:09:51 -0700862 // Init arc whenever System Audio Mode is on
Amy03afe482018-09-18 16:57:45 -0700863 // Terminate arc when System Audio Mode is off
864 // Since some TVs don't request ARC on with System Audio Mode on request
865 if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
866 && isDirectConnectToTv()) {
867 if (newSystemAudioMode && !isArcEnabled()) {
868 removeAction(ArcInitiationActionFromAvr.class);
869 addAndStartAction(new ArcInitiationActionFromAvr(this));
870 } else if (!newSystemAudioMode && isArcEnabled()) {
871 removeAction(ArcTerminationActionFromAvr.class);
872 addAndStartAction(new ArcTerminationActionFromAvr(this));
873 }
Amyacc1dab2018-09-06 17:09:51 -0700874 }
Amy9b91e8c2018-06-11 17:26:26 -0700875 }
876
Amyac1a55c2018-07-13 16:38:23 -0700877 protected void switchToAudioInput() {
878 // TODO(b/111396634): switch input according to PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT
879 }
880
Amy77c7c982018-07-23 18:27:36 -0700881 protected boolean isDirectConnectToTv() {
882 int myPhysicalAddress = mService.getPhysicalAddress();
883 return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress;
884 }
885
Amy9b91e8c2018-06-11 17:26:26 -0700886 private void updateAudioManagerForSystemAudio(boolean on) {
887 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
888 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
889 }
890
Amy79db52f2018-10-23 12:45:17 -0700891 void onSystemAduioControlFeatureSupportChanged(boolean enabled) {
892 setSystemAudioControlFeatureEnabled(enabled);
893 if (enabled) {
894 addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
895 }
896 }
897
Nick Chalko167529c2018-07-18 13:11:31 -0700898 @ServiceThreadOnly
899 void setSystemAudioControlFeatureEnabled(boolean enabled) {
900 assertRunOnServiceThread();
901 synchronized (mLock) {
902 mSystemAudioControlFeatureEnabled = enabled;
903 }
904 }
905
shubangd932cb52018-09-14 17:55:16 -0700906 @ServiceThreadOnly
Amy0c2e29f2018-10-23 12:17:52 -0700907 void setRoutingControlFeatureEnables(boolean enabled) {
908 assertRunOnServiceThread();
909 synchronized (mLock) {
910 mRoutingControlFeatureEnabled = enabled;
911 }
912 }
913
914 @ServiceThreadOnly
shubangd932cb52018-09-14 17:55:16 -0700915 void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
916 assertRunOnServiceThread();
917 // TODO: validate port ID
918 if (portId == getLocalActivePort()) {
919 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
920 return;
921 }
shubangd932cb52018-09-14 17:55:16 -0700922 if (!mService.isControlEnabled()) {
Amyf9fa6172018-10-11 16:47:12 -0700923 setRoutingPort(portId);
shubangd932cb52018-09-14 17:55:16 -0700924 setLocalActivePort(portId);
925 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
926 return;
927 }
Amyf9fa6172018-10-11 16:47:12 -0700928 int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
929 ? mService.portIdToPath(getRoutingPort())
shubangd932cb52018-09-14 17:55:16 -0700930 : getDeviceInfo().getPhysicalAddress();
Amyf9fa6172018-10-11 16:47:12 -0700931 int newPath = mService.portIdToPath(portId);
shubangd932cb52018-09-14 17:55:16 -0700932 if (oldPath == newPath) {
933 return;
934 }
Amyf9fa6172018-10-11 16:47:12 -0700935 setRoutingPort(portId);
shubangd932cb52018-09-14 17:55:16 -0700936 setLocalActivePort(portId);
937 HdmiCecMessage routingChange =
938 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
939 mService.sendCecCommand(routingChange);
940 }
941
Nick Chalko167529c2018-07-18 13:11:31 -0700942 boolean isSystemAudioControlFeatureEnabled() {
Amy9b91e8c2018-06-11 17:26:26 -0700943 synchronized (mLock) {
944 return mSystemAudioControlFeatureEnabled;
945 }
946 }
Nick Chalkof28c7b52018-06-14 07:46:00 -0700947
Shubang595a1832018-06-27 17:52:18 -0700948 protected boolean isSystemAudioActivated() {
949 synchronized (mLock) {
950 return mSystemAudioActivated;
951 }
952 }
953
Shubang2fd186e2018-07-18 16:53:12 -0700954 protected void terminateSystemAudioMode() {
955 // remove pending initiation actions
956 removeAction(SystemAudioInitiationActionFromAvr.class);
Shubangbc2aab32018-07-19 16:41:17 -0700957 if (!isSystemAudioActivated()) {
958 return;
Shubang2fd186e2018-07-18 16:53:12 -0700959 }
960
Amy06dc4cd2018-08-09 18:22:40 -0700961 if (checkSupportAndSetSystemAudioMode(false)) {
Shubangbc2aab32018-07-19 16:41:17 -0700962 // send <Set System Audio Mode> [“Off”]
963 mService.sendCecCommand(
964 HdmiCecMessageBuilder.buildSetSystemAudioMode(
965 mAddress, Constants.ADDR_BROADCAST, false));
Shubang2fd186e2018-07-18 16:53:12 -0700966 }
967 }
968
Nick Chalkof28c7b52018-06-14 07:46:00 -0700969 /** Reports if System Audio Mode is supported by the connected TV */
970 interface TvSystemAudioModeSupportedCallback {
971
972 /** {@code supported} is true if the TV is connected and supports System Audio Mode. */
973 void onResult(boolean supported);
Nick Chalkof28c7b52018-06-14 07:46:00 -0700974 }
975
976 /**
977 * Queries the connected TV to detect if System Audio Mode is supported by the TV.
978 *
979 * <p>This query may take up to 2 seconds to complete.
980 *
981 * <p>The result of the query may be cached until Audio device type is put in standby or loses
982 * its physical address.
983 */
Amy06dc4cd2018-08-09 18:22:40 -0700984 // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic.
Nick Chalkof28c7b52018-06-14 07:46:00 -0700985 void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
Shubangec668962018-07-13 19:03:19 -0700986 if (!mTvSystemAudioModeSupport) {
987 addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
988 } else {
989 callback.onResult(true);
990 }
991 }
992
Amy06dc4cd2018-08-09 18:22:40 -0700993 /**
994 * Handler of System Audio Mode Request on from non TV device
995 */
996 void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
997 if (!isSystemAudioControlFeatureEnabled()) {
998 HdmiLogger.debug(
999 "Cannot turn on" + "system audio mode "
1000 + "because the System Audio Control feature is disabled.");
1001 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1002 return;
1003 }
Amyae4ee342018-10-09 16:55:14 -07001004 // Wake up device
1005 mService.wakeUp();
Amy06dc4cd2018-08-09 18:22:40 -07001006 // Check if TV supports System Audio Control.
1007 // Handle broadcasting setSystemAudioMode on or aborting message on callback.
1008 queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
1009 public void onResult(boolean supported) {
1010 if (supported) {
1011 setSystemAudioMode(true);
1012 mService.sendCecCommand(
1013 HdmiCecMessageBuilder.buildSetSystemAudioMode(
1014 mAddress, Constants.ADDR_BROADCAST, true));
1015 } else {
1016 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1017 }
1018 }
1019 });
1020 }
1021
Shubangec668962018-07-13 19:03:19 -07001022 void setTvSystemAudioModeSupport(boolean supported) {
1023 mTvSystemAudioModeSupport = supported;
Nick Chalkof28c7b52018-06-14 07:46:00 -07001024 }
Amy77c7c982018-07-23 18:27:36 -07001025
1026 @VisibleForTesting
1027 protected boolean isArcEnabled() {
1028 synchronized (mLock) {
1029 return mArcEstablished;
1030 }
1031 }
Amy93a97b92018-08-17 14:58:41 -07001032
Amy225d55a2018-09-06 11:03:51 -07001033 @Override
1034 protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
Amy00638112018-10-31 17:47:17 -07001035 int port = mService.pathToPortId(physicalAddress);
Amy34037422018-09-06 13:21:08 -07001036 if (isSystemAudioActivated() && port < 0) {
1037 // If system audio mode is on and the new active source is not under the current device,
1038 // Will switch to ARC input.
1039 // TODO(b/115637145): handle system aduio without ARC
Amy225d55a2018-09-06 11:03:51 -07001040 routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
Amy34037422018-09-06 13:21:08 -07001041 } else if (mIsSwitchDevice && port >= 0) {
1042 // If current device is a switch and the new active source is under it,
1043 // will switch to the corresponding active path.
1044 routeToInputFromPortId(port);
Amy225d55a2018-09-06 11:03:51 -07001045 }
1046 }
1047
Amy225d55a2018-09-06 11:03:51 -07001048 protected void routeToInputFromPortId(int portId) {
Amy0c2e29f2018-10-23 12:17:52 -07001049 if (!isRoutingControlFeatureEnabled()) {
1050 HdmiLogger.debug("Routing Control Feature is not enabled.");
1051 return;
1052 }
Amy225d55a2018-09-06 11:03:51 -07001053 if (mArcIntentUsed) {
1054 routeToTvInputFromPortId(portId);
1055 } else {
1056 // TODO(): implement input switching for devices not using TvInput.
1057 }
1058 }
1059
1060 protected void routeToTvInputFromPortId(int portId) {
1061 if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
1062 HdmiLogger.debug("Invalid port number for Tv Input switching.");
1063 return;
1064 }
Amy03afe482018-09-18 16:57:45 -07001065 // Wake up if the current device if ready to route.
Amyae4ee342018-10-09 16:55:14 -07001066 mService.wakeUp();
Amy34037422018-09-06 13:21:08 -07001067 if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
Amy225d55a2018-09-06 11:03:51 -07001068 switchToHomeTvInput();
1069 } else if (portId == Constants.CEC_SWITCH_ARC) {
1070 switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
Amy03afe482018-09-18 16:57:45 -07001071 setLocalActivePort(portId);
Amy225d55a2018-09-06 11:03:51 -07001072 return;
1073 } else {
Amy34037422018-09-06 13:21:08 -07001074 String uri = mTvInputs.get(portId);
1075 if (uri != null) {
1076 switchToTvInput(mTvInputs.get(portId));
1077 } else {
1078 HdmiLogger.debug("Port number does not match any Tv Input.");
1079 return;
1080 }
Amy225d55a2018-09-06 11:03:51 -07001081 }
1082
1083 setLocalActivePort(portId);
Amy03afe482018-09-18 16:57:45 -07001084 setRoutingPort(portId);
Amy225d55a2018-09-06 11:03:51 -07001085 }
1086
1087 // For device to switch to specific TvInput with corresponding URI.
1088 private void switchToTvInput(String uri) {
Amy4aef3b402019-01-18 14:17:55 -08001089 try {
1090 mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
1091 TvContract.buildChannelUriForPassthroughInput(uri))
1092 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
1093 } catch (ActivityNotFoundException e) {
1094 Slog.e(TAG, "Can't find activity to switch to " + uri, e);
1095 }
Amy225d55a2018-09-06 11:03:51 -07001096 }
1097
1098 // For device using TvInput to switch to Home.
1099 private void switchToHomeTvInput() {
Amy4aef3b402019-01-18 14:17:55 -08001100 try {
1101 Intent activityIntent = new Intent(Intent.ACTION_MAIN)
1102 .addCategory(Intent.CATEGORY_HOME)
1103 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
1104 | Intent.FLAG_ACTIVITY_SINGLE_TOP
1105 | Intent.FLAG_ACTIVITY_NEW_TASK
1106 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
1107 mService.getContext().startActivity(activityIntent);
1108 } catch (ActivityNotFoundException e) {
1109 Slog.e(TAG, "Can't find activity to switch to HOME", e);
1110 }
Amy225d55a2018-09-06 11:03:51 -07001111 }
Amy79b54e92018-09-11 17:02:28 -07001112
1113 @Override
1114 protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
Amy00638112018-10-31 17:47:17 -07001115 int port = mService.pathToPortId(physicalAddress);
Amy79b54e92018-09-11 17:02:28 -07001116 // Routing change or information sent from switches under the current device can be ignored.
1117 if (port > 0) {
1118 return;
1119 }
1120 // When other switches route to some other devices not under the current device,
1121 // check system audio mode status and do ARC switch if needed.
1122 if (port < 0 && isSystemAudioActivated()) {
1123 handleRoutingChangeAndInformationForSystemAudio();
1124 return;
1125 }
1126 // When other switches route to the current device
1127 // and the current device is also a switch.
1128 if (port == 0) {
1129 handleRoutingChangeAndInformationForSwitch(message);
1130 }
1131 }
1132
1133 // Handle the system audio(ARC) part of the logic on receiving routing change or information.
1134 private void handleRoutingChangeAndInformationForSystemAudio() {
Amy79b54e92018-09-11 17:02:28 -07001135 // TODO(b/115637145): handle system aduio without ARC
1136 routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
1137 }
1138
1139 // Handle the routing control part of the logic on receiving routing change or information.
1140 private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
Amy03afe482018-09-18 16:57:45 -07001141 if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
Amy79b54e92018-09-11 17:02:28 -07001142 routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
Amy123ec402018-09-25 10:56:31 -07001143 mService.setAndBroadcastActiveSourceFromOneDeviceType(
1144 message.getSource(), mService.getPhysicalAddress());
Amy79b54e92018-09-11 17:02:28 -07001145 return;
1146 }
1147
Amy238f64f2018-11-30 11:27:56 -08001148 int routingInformationPath = mService.portIdToPath(getRoutingPort());
Amy79b54e92018-09-11 17:02:28 -07001149 // If current device is already the leaf of the whole HDMI system, will do nothing.
1150 if (routingInformationPath == mService.getPhysicalAddress()) {
1151 HdmiLogger.debug("Current device can't assign valid physical address"
1152 + "to devices under it any more. "
Amy4aef3b402019-01-18 14:17:55 -08001153 + "It's physical address is "
1154 + routingInformationPath);
Amy79b54e92018-09-11 17:02:28 -07001155 return;
1156 }
1157 // Otherwise will switch to the current active port and broadcast routing information.
1158 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
1159 mAddress, routingInformationPath));
Amy03afe482018-09-18 16:57:45 -07001160 routeToInputFromPortId(getRoutingPort());
Amy79b54e92018-09-11 17:02:28 -07001161 }
Amy1e4a8cc2018-10-15 10:18:14 -07001162
1163 protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1164 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1165 if (info == null) {
1166 Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1167 return;
1168 }
1169
1170 if (info.getDevicePowerStatus() == newPowerStatus) {
1171 return;
1172 }
1173
1174 HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1175 // addDeviceInfo replaces old device info with new one if exists.
1176 addDeviceInfo(newInfo);
1177
1178 invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1179 }
Amyc00cd4e2018-10-16 20:21:33 -07001180
1181 @ServiceThreadOnly
1182 private void launchDeviceDiscovery() {
1183 assertRunOnServiceThread();
Amy47fe0b12018-11-06 14:45:18 -08001184 if (hasAction(DeviceDiscoveryAction.class)) {
1185 Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
1186 removeAction(DeviceDiscoveryAction.class);
1187 }
Amyc00cd4e2018-10-16 20:21:33 -07001188 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
1189 new DeviceDiscoveryCallback() {
1190 @Override
1191 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
1192 for (HdmiDeviceInfo info : deviceInfos) {
1193 addCecDevice(info);
1194 }
1195 }
1196 });
1197 addAndStartAction(action);
1198 }
1199
1200 // Clear all device info.
1201 @ServiceThreadOnly
1202 private void clearDeviceInfoList() {
1203 assertRunOnServiceThread();
1204 for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
1205 if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
1206 continue;
1207 }
1208 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1209 }
1210 mDeviceInfos.clear();
Amyec126a52018-10-30 16:51:14 -07001211 updateSafeDeviceInfoList();
Amyc00cd4e2018-10-16 20:21:33 -07001212 }
Nick Chalkob9e48e22018-10-23 06:59:39 -07001213
1214 @Override
1215 protected void dump(IndentingPrintWriter pw) {
1216 pw.println("HdmiCecLocalDeviceAudioSystem:");
1217 pw.increaseIndent();
1218 pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
Amy77e672c2018-10-31 15:55:40 -07001219 pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
Nick Chalkob9e48e22018-10-23 06:59:39 -07001220 pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1221 pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
1222 pw.println("mArcEstablished: " + mArcEstablished);
1223 pw.println("mArcIntentUsed: " + mArcIntentUsed);
Amy2e4b25c2018-10-30 18:08:49 -07001224 pw.println("mRoutingPort: " + getRoutingPort());
1225 pw.println("mLocalActivePort: " + getLocalActivePort());
Nick Chalkob9e48e22018-10-23 06:59:39 -07001226 HdmiUtils.dumpMap(pw, "mTvInputs:", mTvInputs);
1227 HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
1228 pw.decreaseIndent();
1229 super.dump(pw);
1230 }
Nick Chalkob89d6ff2018-05-25 10:29:01 -07001231}