blob: 923b87d4ce5601109d16e595a6a4f578b4a9b6e0 [file] [log] [blame]
Jinsuk Kim2918e9e2014-05-20 16:45:45 +09001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.hdmi;
18
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090019import android.content.Intent;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090020import android.hardware.hdmi.HdmiCecDeviceInfo;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090021import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang60cffce2014-06-12 18:03:04 +090022import android.hardware.hdmi.IHdmiControlCallback;
Jungshik Jang6f34f5a2014-07-08 21:17:29 +090023import android.media.AudioManager.OnAudioPortUpdateListener;
24import android.media.AudioPatch;
25import android.media.AudioPort;
Jungshik Janga858d222014-06-23 17:17:47 +090026import android.media.AudioSystem;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090027import android.os.RemoteException;
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090028import android.os.UserHandle;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090029import android.provider.Settings.Global;
Jungshik Jang092b4452014-06-11 15:19:17 +090030import android.util.Slog;
Jungshik Jang79c58a42014-06-16 16:45:36 +090031import android.util.SparseArray;
Jungshik Jang092b4452014-06-11 15:19:17 +090032
Jungshik Jang79c58a42014-06-16 16:45:36 +090033import com.android.internal.annotations.GuardedBy;
Jungshik Jang60cffce2014-06-12 18:03:04 +090034import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
Jungshik Janga5b74142014-06-23 18:03:10 +090035import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang60cffce2014-06-12 18:03:04 +090036
Jungshik Jang8f2ed352014-07-07 15:02:47 +090037import java.io.UnsupportedEncodingException;
Jungshik Jang79c58a42014-06-16 16:45:36 +090038import java.util.ArrayList;
Jinsuk Kim0a3316b2014-06-14 09:33:55 +090039import java.util.Collections;
40import java.util.List;
Jungshik Jang092b4452014-06-11 15:19:17 +090041import java.util.Locale;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090042
43/**
44 * Represent a logical device of type TV residing in Android system.
45 */
46final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
Jungshik Jang092b4452014-06-11 15:19:17 +090047 private static final String TAG = "HdmiCecLocalDeviceTv";
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090048
Jungshik Janga13da0d2014-06-30 16:26:06 +090049 // Whether ARC is available or not. "true" means that ARC is estabilished between TV and
50 // AVR as audio receiver.
51 @ServiceThreadOnly
52 private boolean mArcEstablished = false;
53
54 // Whether ARC feature is enabled or not.
55 private boolean mArcFeatureEnabled = false;
Jungshik Jang79c58a42014-06-16 16:45:36 +090056
Jungshik Jang79c58a42014-06-16 16:45:36 +090057 // Whether SystemAudioMode is "On" or not.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090058 @GuardedBy("mLock")
Jungshik Jang79c58a42014-06-16 16:45:36 +090059 private boolean mSystemAudioMode;
60
Jinsuk Kim83335712014-06-24 07:57:00 +090061 // The previous port id (input) before switching to the new one. This is remembered in order to
62 // be able to switch to it upon receiving <Inactive Source> from currently active source.
63 // This remains valid only when the active source was switched via one touch play operation
64 // (either by TV or source device). Manual port switching invalidates this value to
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090065 // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
Jinsuk Kim83335712014-06-24 07:57:00 +090066 @GuardedBy("mLock")
67 private int mPrevPortId;
68
Jungshik Jang8fa36b12014-06-25 15:51:36 +090069 @GuardedBy("mLock")
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090070 private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
Jungshik Jang8fa36b12014-06-25 15:51:36 +090071
72 @GuardedBy("mLock")
73 private boolean mSystemAudioMute = false;
74
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090075 // Copy of mDeviceInfos to guarantee thread-safety.
76 @GuardedBy("mLock")
77 private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090078 // All external cec input(source) devices. Does not include system audio device.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090079 @GuardedBy("mLock")
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090080 private List<HdmiCecDeviceInfo> mSafeExternalInputs = Collections.emptyList();
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090081
Jungshik Jang79c58a42014-06-16 16:45:36 +090082 // Map-like container of all cec devices including local ones.
83 // A logical address of device is used as key of container.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090084 // This is not thread-safe. For external purpose use mSafeDeviceInfos.
Jungshik Jang79c58a42014-06-16 16:45:36 +090085 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>();
86
Jinsuk Kim160a6e52014-07-02 06:16:36 +090087 // If true, TV going to standby mode puts other devices also to standby.
88 private boolean mAutoDeviceOff;
89
Jungshik Jang3ee65722014-06-03 16:22:30 +090090 HdmiCecLocalDeviceTv(HdmiControlService service) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090091 super(service, HdmiCecDeviceInfo.DEVICE_TV);
92 mPrevPortId = Constants.INVALID_PORT_ID;
Jungshik Jang8b308d92014-05-29 21:52:28 +090093 }
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090094
Jungshik Jang8b308d92014-05-29 21:52:28 +090095 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +090096 @ServiceThreadOnly
Jungshik Janga9f10622014-07-11 15:36:39 +090097 protected void onAddressAllocated(int logicalAddress, boolean fromBootup) {
Jungshik Jang79c58a42014-06-16 16:45:36 +090098 assertRunOnServiceThread();
Jungshik Jang3ee65722014-06-03 16:22:30 +090099 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
100 mAddress, mService.getPhysicalAddress(), mDeviceType));
101 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
102 mAddress, mService.getVendorId()));
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900103 mSystemAudioMode = mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
Jungshik Janga9f10622014-07-11 15:36:39 +0900104 launchRoutingControl(fromBootup);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900105 launchDeviceDiscovery();
Jungshik Jang6f34f5a2014-07-08 21:17:29 +0900106 registerAudioPortUpdateListener();
107 // TODO: unregister audio port update listener if local device is released.
108 }
109
110 private void registerAudioPortUpdateListener() {
111 mService.getAudioManager().registerAudioPortUpdateListener(
112 new OnAudioPortUpdateListener() {
113 @Override
114 public void OnAudioPatchListUpdate(AudioPatch[] patchList) {}
115
116 @Override
117 public void OnAudioPortListUpdate(AudioPort[] portList) {
118 if (!mSystemAudioMode) {
119 return;
120 }
121 int devices = mService.getAudioManager().getDevicesForStream(
122 AudioSystem.STREAM_MUSIC);
123 if ((devices & ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER)
124 != 0) {
125 // TODO: release system audio here.
126 }
127 }
128
129 @Override
130 public void OnServiceDied() {}
131 });
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900132 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900133
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900134 /**
135 * Performs the action 'device select', or 'one touch play' initiated by TV.
136 *
137 * @param targetAddress logical address of the device to select
138 * @param callback callback object to report the result with
139 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900140 @ServiceThreadOnly
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900141 void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900142 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900143 if (targetAddress == Constants.ADDR_INTERNAL) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900144 handleSelectInternalSource(callback);
145 return;
146 }
Jinsuk Kim13c030e2014-06-20 13:25:17 +0900147 HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900148 if (targetDevice == null) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900149 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900150 return;
151 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900152 removeAction(DeviceSelectAction.class);
153 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900154 }
155
Jungshik Janga5b74142014-06-23 18:03:10 +0900156 @ServiceThreadOnly
Jinsuk Kim83335712014-06-24 07:57:00 +0900157 private void handleSelectInternalSource(IHdmiControlCallback callback) {
Jinsuk Kima062a932014-06-18 10:00:39 +0900158 assertRunOnServiceThread();
Jinsuk Kim83335712014-06-24 07:57:00 +0900159 // Seq #18
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900160 if (mService.isControlEnabled() && getActiveSource() != mAddress) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900161 updateActiveSource(mAddress, mService.getPhysicalAddress());
162 // TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
163 HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
164 mAddress, mService.getPhysicalAddress());
165 mService.sendCecCommand(activeSource);
166 }
167 }
168
169 @ServiceThreadOnly
170 void updateActiveSource(int activeSource, int activePath) {
171 assertRunOnServiceThread();
172 // Seq #14
173 if (activeSource == getActiveSource() && activePath == getActivePath()) {
174 return;
175 }
176 setActiveSource(activeSource);
177 setActivePath(activePath);
178 if (getDeviceInfo(activeSource) != null && activeSource != mAddress) {
179 if (mService.pathToPortId(activePath) == getActivePortId()) {
180 setPrevPortId(getActivePortId());
181 }
182 // TODO: Show the OSD banner related to the new active source device.
183 } else {
184 // TODO: If displayed, remove the OSD banner related to the previous
185 // active source device.
186 }
187 }
188
189 /**
190 * Returns the previous port id kept to handle input switching on <Inactive Source>.
191 */
192 int getPrevPortId() {
193 synchronized (mLock) {
194 return mPrevPortId;
195 }
196 }
197
198 /**
199 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
200 * taken for <Inactive Source>.
201 */
202 void setPrevPortId(int portId) {
203 synchronized (mLock) {
204 mPrevPortId = portId;
205 }
206 }
207
208 @ServiceThreadOnly
209 void updateActivePortId(int portId) {
210 assertRunOnServiceThread();
211 // Seq #15
212 if (portId == getActivePortId()) {
213 return;
214 }
215 setPrevPortId(portId);
216 // TODO: Actually switch the physical port here. Handle PAP/PIP as well.
217 // Show OSD port change banner
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900218 mService.invokeInputChangeListener(getActiveSource());
Jinsuk Kim83335712014-06-24 07:57:00 +0900219 }
220
221 @ServiceThreadOnly
222 void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
223 assertRunOnServiceThread();
224 // Seq #20
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900225 if (!mService.isControlEnabled() || portId == getActivePortId()) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900226 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
Jinsuk Kima062a932014-06-18 10:00:39 +0900227 return;
228 }
Jinsuk Kim83335712014-06-24 07:57:00 +0900229 // TODO: Make sure this call does not stem from <Active Source> message reception.
Jinsuk Kima062a932014-06-18 10:00:39 +0900230
Jinsuk Kim83335712014-06-24 07:57:00 +0900231 setActivePortId(portId);
Jinsuk Kima062a932014-06-18 10:00:39 +0900232 // TODO: Return immediately if the operation is triggered by <Text/Image View On>
Jinsuk Kim83335712014-06-24 07:57:00 +0900233 // and this is the first notification about the active input after power-on.
Jinsuk Kima062a932014-06-18 10:00:39 +0900234 // TODO: Handle invalid port id / active input which should be treated as an
Jinsuk Kim83335712014-06-24 07:57:00 +0900235 // internal tuner.
Jinsuk Kima062a932014-06-18 10:00:39 +0900236
237 removeAction(RoutingControlAction.class);
238
239 int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
240 int newPath = mService.portIdToPath(portId);
241 HdmiCecMessage routingChange =
242 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
243 mService.sendCecCommand(routingChange);
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900244 addAndStartAction(new RoutingControlAction(this, newPath, false, callback));
Jinsuk Kima062a932014-06-18 10:00:39 +0900245 }
246
Jinsuk Kimcae66272014-07-04 13:26:44 +0900247 int getPowerStatus() {
248 return mService.getPowerStatus();
249 }
250
Jinsuk Kima062a932014-06-18 10:00:39 +0900251 /**
252 * Sends key to a target CEC device.
253 *
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900254 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900255 * @param isPressed true if this is key press event
Jinsuk Kima062a932014-06-18 10:00:39 +0900256 */
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900257 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900258 @ServiceThreadOnly
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900259 protected void sendKeyEvent(int keyCode, boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +0900260 assertRunOnServiceThread();
261 List<SendKeyAction> action = getActions(SendKeyAction.class);
262 if (!action.isEmpty()) {
263 action.get(0).processKeyEvent(keyCode, isPressed);
264 } else {
265 if (isPressed) {
266 addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode));
267 } else {
268 Slog.w(TAG, "Discard key release event");
269 }
270 }
271 }
272
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900273 private static void invokeCallback(IHdmiControlCallback callback, int result) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900274 if (callback == null) {
275 return;
276 }
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900277 try {
278 callback.onComplete(result);
279 } catch (RemoteException e) {
280 Slog.e(TAG, "Invoking callback failed:" + e);
281 }
282 }
283
Jungshik Jang092b4452014-06-11 15:19:17 +0900284 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900285 @ServiceThreadOnly
Jinsuk Kim83335712014-06-24 07:57:00 +0900286 protected boolean handleActiveSource(HdmiCecMessage message) {
287 assertRunOnServiceThread();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900288 int address = message.getSource();
289 int path = HdmiUtils.twoBytesToInt(message.getParams());
290 if (getDeviceInfo(address) == null) {
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900291 handleNewDeviceAtTheTailOfActivePath(path);
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900292 } else {
293 ActiveSourceHandler.create(this, null).process(address, path);
294 }
Jinsuk Kim83335712014-06-24 07:57:00 +0900295 return true;
296 }
297
298 @Override
299 @ServiceThreadOnly
300 protected boolean handleInactiveSource(HdmiCecMessage message) {
301 assertRunOnServiceThread();
302 // Seq #10
303
304 // Ignore <Inactive Source> from non-active source device.
305 if (getActiveSource() != message.getSource()) {
306 return true;
307 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900308 if (isProhibitMode()) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900309 return true;
310 }
311 int portId = getPrevPortId();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900312 if (portId != Constants.INVALID_PORT_ID) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900313 // TODO: Do this only if TV is not showing multiview like PIP/PAP.
314
315 HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource());
316 if (inactiveSource == null) {
317 return true;
318 }
319 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
320 return true;
321 }
322 // TODO: Switch the TV freeze mode off
323
324 setActivePortId(portId);
325 doManualPortSwitching(portId, null);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900326 setPrevPortId(Constants.INVALID_PORT_ID);
Jinsuk Kim83335712014-06-24 07:57:00 +0900327 }
328 return true;
329 }
330
331 @Override
332 @ServiceThreadOnly
333 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
334 assertRunOnServiceThread();
335 // Seq #19
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900336 if (mAddress == getActiveSource()) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900337 mService.sendCecCommand(
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900338 HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
Jinsuk Kim83335712014-06-24 07:57:00 +0900339 }
340 return true;
341 }
342
343 @Override
344 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900345 protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900346 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900347 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
348 mAddress, Locale.getDefault().getISO3Language());
349 // TODO: figure out how to handle failed to get language code.
350 if (command != null) {
351 mService.sendCecCommand(command);
352 } else {
353 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
354 }
355 return true;
356 }
357
Jungshik Jang60cffce2014-06-12 18:03:04 +0900358 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900359 @ServiceThreadOnly
Jungshik Jang60cffce2014-06-12 18:03:04 +0900360 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900361 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900362 // Ignore if [Device Discovery Action] is going on.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900363 if (hasAction(DeviceDiscoveryAction.class)) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900364 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
365 + "because Device Discovery Action is on-going:" + message);
366 return true;
367 }
368
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900369 int path = HdmiUtils.twoBytesToInt(message.getParams());
370 int address = message.getSource();
371 if (!isInDeviceList(path, address)) {
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900372 handleNewDeviceAtTheTailOfActivePath(path);
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900373 }
374 addAndStartAction(new NewDeviceAction(this, address, path));
375 return true;
376 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900377
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900378 private void handleNewDeviceAtTheTailOfActivePath(int path) {
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900379 // Seq #22
380 if (isTailOfActivePath(path, getActivePath())) {
381 removeAction(RoutingControlAction.class);
382 int newPath = mService.portIdToPath(getActivePortId());
383 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
384 mAddress, getActivePath(), newPath));
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900385 addAndStartAction(new RoutingControlAction(this, getActivePortId(), false, null));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900386 }
387 }
388
389 /**
390 * Whether the given path is located in the tail of current active path.
391 *
392 * @param path to be tested
393 * @param activePath current active path
394 * @return true if the given path is located in the tail of current active path; otherwise,
395 * false
396 */
397 static boolean isTailOfActivePath(int path, int activePath) {
398 // If active routing path is internal source, return false.
399 if (activePath == 0) {
400 return false;
401 }
402 for (int i = 12; i >= 0; i -= 4) {
403 int curActivePath = (activePath >> i) & 0xF;
404 if (curActivePath == 0) {
405 return true;
406 } else {
407 int curPath = (path >> i) & 0xF;
408 if (curPath != curActivePath) {
409 return false;
410 }
411 }
412 }
413 return false;
414 }
415
416 @Override
417 @ServiceThreadOnly
418 protected boolean handleRoutingChange(HdmiCecMessage message) {
419 assertRunOnServiceThread();
420 // Seq #21
421 byte[] params = message.getParams();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900422 int currentPath = HdmiUtils.twoBytesToInt(params);
423 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
424 int newPath = HdmiUtils.twoBytesToInt(params, 2);
425 setActivePath(newPath);
426 removeAction(RoutingControlAction.class);
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900427 addAndStartAction(new RoutingControlAction(this, newPath, true, null));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900428 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900429 return true;
430 }
Jinsuk Kim0a3316b2014-06-14 09:33:55 +0900431
432 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900433 @ServiceThreadOnly
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900434 protected boolean handleReportAudioStatus(HdmiCecMessage message) {
435 assertRunOnServiceThread();
436
437 byte params[] = message.getParams();
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900438 int mute = params[0] & 0x80;
439 int volume = params[0] & 0x7F;
440 setAudioStatus(mute == 0x80, volume);
441 return true;
442 }
443
Yuncheol Heo38db6292014-07-01 14:15:14 +0900444 @Override
445 @ServiceThreadOnly
446 protected boolean handleTextViewOn(HdmiCecMessage message) {
447 assertRunOnServiceThread();
448 if (mService.isPowerStandbyOrTransient()) {
449 mService.wakeUp();
450 }
451 // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel
452 // that represents the source device.
453 return true;
454 }
455
456 @Override
457 @ServiceThreadOnly
458 protected boolean handleImageViewOn(HdmiCecMessage message) {
459 assertRunOnServiceThread();
460 // Currently, it's the same as <Text View On>.
461 return handleTextViewOn(message);
462 }
463
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900464 @Override
465 @ServiceThreadOnly
466 protected boolean handleSetOsdName(HdmiCecMessage message) {
467 int source = message.getSource();
468 HdmiCecDeviceInfo deviceInfo = getDeviceInfo(source);
469 // If the device is not in device list, ignore it.
470 if (deviceInfo == null) {
471 Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
472 return true;
473 }
474 String osdName = null;
475 try {
476 osdName = new String(message.getParams(), "US-ASCII");
477 } catch (UnsupportedEncodingException e) {
478 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
479 return true;
480 }
481
482 if (deviceInfo.getDisplayName().equals(osdName)) {
483 Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
484 return true;
485 }
486
487 addCecDevice(new HdmiCecDeviceInfo(deviceInfo.getLogicalAddress(),
488 deviceInfo.getPhysicalAddress(), deviceInfo.getDeviceType(),
489 deviceInfo.getVendorId(), osdName));
490 return true;
491 }
492
Jungshik Janga5b74142014-06-23 18:03:10 +0900493 @ServiceThreadOnly
Jungshik Jang60cffce2014-06-12 18:03:04 +0900494 private void launchDeviceDiscovery() {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900495 assertRunOnServiceThread();
496 clearDeviceInfoList();
497 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
Jungshik Jang60cffce2014-06-12 18:03:04 +0900498 new DeviceDiscoveryCallback() {
499 @Override
500 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
501 for (HdmiCecDeviceInfo info : deviceInfos) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900502 addCecDevice(info);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900503 }
504
505 // Since we removed all devices when it's start and
506 // device discovery action does not poll local devices,
507 // we should put device info of local device manually here
508 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900509 addCecDevice(device.getDeviceInfo());
Jungshik Jang60cffce2014-06-12 18:03:04 +0900510 }
511
Jungshik Jang79c58a42014-06-16 16:45:36 +0900512 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
Jungshik Jang187d0172014-06-17 17:48:42 +0900513
514 // If there is AVR, initiate System Audio Auto initiation action,
515 // which turns on and off system audio according to last system
516 // audio setting.
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900517 if (mSystemAudioMode && getAvrDeviceInfo() != null) {
Jungshik Jang187d0172014-06-17 17:48:42 +0900518 addAndStartAction(new SystemAudioAutoInitiationAction(
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900519 HdmiCecLocalDeviceTv.this,
520 getAvrDeviceInfo().getLogicalAddress()));
Jungshik Janga13da0d2014-06-30 16:26:06 +0900521 if (mArcEstablished) {
522 startArcAction(true);
Jungshik Jang5e3916a2014-06-30 14:59:21 +0900523 }
Jungshik Jang187d0172014-06-17 17:48:42 +0900524 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900525 }
526 });
Jungshik Jang79c58a42014-06-16 16:45:36 +0900527 addAndStartAction(action);
528 }
529
530 // Clear all device info.
Jungshik Janga5b74142014-06-23 18:03:10 +0900531 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900532 private void clearDeviceInfoList() {
533 assertRunOnServiceThread();
534 mDeviceInfos.clear();
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900535 updateSafeDeviceInfoList();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900536 }
537
Jungshik Janga5b74142014-06-23 18:03:10 +0900538 @ServiceThreadOnly
Jungshik Jangea67c182014-06-19 22:19:20 +0900539 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
540 assertRunOnServiceThread();
541 HdmiCecDeviceInfo avr = getAvrDeviceInfo();
542 if (avr == null) {
Yuncheol Heod05f67f2014-07-11 16:06:40 +0900543 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +0900544 return;
545 }
546
547 addAndStartAction(
548 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
Jungshik Jangea67c182014-06-19 22:19:20 +0900549 }
550
Jungshik Jangca5be9a2014-07-01 18:01:26 +0900551 // # Seq 25
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900552 void setSystemAudioMode(boolean on, boolean updateSetting) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900553 synchronized (mLock) {
554 if (on != mSystemAudioMode) {
555 mSystemAudioMode = on;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900556 if (updateSetting) {
557 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
558 }
Jungshik Jangea67c182014-06-19 22:19:20 +0900559 mService.announceSystemAudioModeChange(on);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900560 }
561 }
562 }
563
564 boolean getSystemAudioMode() {
565 synchronized (mLock) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900566 return mSystemAudioMode;
567 }
568 }
569
570 /**
571 * Change ARC status into the given {@code enabled} status.
572 *
573 * @return {@code true} if ARC was in "Enabled" status
574 */
Jungshik Janga13da0d2014-06-30 16:26:06 +0900575 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900576 boolean setArcStatus(boolean enabled) {
Jungshik Janga13da0d2014-06-30 16:26:06 +0900577 assertRunOnServiceThread();
578 boolean oldStatus = mArcEstablished;
579 // 1. Enable/disable ARC circuit.
580 mService.setAudioReturnChannel(enabled);
581 // 2. Notify arc status to audio service.
582 notifyArcStatusToAudioService(enabled);
583 // 3. Update arc status;
584 mArcEstablished = enabled;
585 return oldStatus;
Jungshik Jang79c58a42014-06-16 16:45:36 +0900586 }
587
Jungshik Janga858d222014-06-23 17:17:47 +0900588 private void notifyArcStatusToAudioService(boolean enabled) {
589 // Note that we don't set any name to ARC.
590 mService.getAudioManager().setWiredDeviceConnectionState(
591 AudioSystem.DEVICE_OUT_HDMI_ARC,
592 enabled ? 1 : 0, "");
593 }
594
Jungshik Jang79c58a42014-06-16 16:45:36 +0900595 /**
596 * Returns whether ARC is enabled or not.
597 */
Jungshik Janga13da0d2014-06-30 16:26:06 +0900598 @ServiceThreadOnly
599 boolean isArcEstabilished() {
600 assertRunOnServiceThread();
601 return mArcFeatureEnabled && mArcEstablished;
602 }
603
604 @ServiceThreadOnly
605 void changeArcFeatureEnabled(boolean enabled) {
606 assertRunOnServiceThread();
607
608 if (mArcFeatureEnabled != enabled) {
609 if (enabled) {
610 if (!mArcEstablished) {
611 startArcAction(true);
612 }
613 } else {
614 if (mArcEstablished) {
615 startArcAction(false);
616 }
617 }
618 mArcFeatureEnabled = enabled;
619 }
620 }
621
622 @ServiceThreadOnly
623 boolean isArcFeatureEnabled() {
624 assertRunOnServiceThread();
625 return mArcFeatureEnabled;
626 }
627
628 @ServiceThreadOnly
629 private void startArcAction(boolean enabled) {
630 assertRunOnServiceThread();
631 HdmiCecDeviceInfo info = getAvrDeviceInfo();
632 if (info == null) {
633 return;
634 }
635 if (!isConnectedToArcPort(info.getPhysicalAddress())) {
636 return;
637 }
638
639 // Terminate opposite action and start action if not exist.
640 if (enabled) {
641 removeAction(RequestArcTerminationAction.class);
642 if (!hasAction(RequestArcInitiationAction.class)) {
643 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
644 }
645 } else {
646 removeAction(RequestArcInitiationAction.class);
647 if (!hasAction(RequestArcTerminationAction.class)) {
648 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
649 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900650 }
651 }
652
653 void setAudioStatus(boolean mute, int volume) {
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900654 synchronized (mLock) {
655 mSystemAudioMute = mute;
656 mSystemAudioVolume = volume;
657 // TODO: pass volume to service (audio service) after scale it to local volume level.
658 mService.setAudioStatus(mute, volume);
659 }
660 }
661
662 @ServiceThreadOnly
663 void changeVolume(int curVolume, int delta, int maxVolume) {
664 assertRunOnServiceThread();
665 if (delta == 0 || !isSystemAudioOn()) {
666 return;
667 }
668
669 int targetVolume = curVolume + delta;
670 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
671 synchronized (mLock) {
672 // If new volume is the same as current system audio volume, just ignore it.
673 // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
674 if (cecVolume == mSystemAudioVolume) {
675 // Update tv volume with system volume value.
676 mService.setAudioStatus(false,
677 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
678 return;
679 }
680 }
681
682 // Remove existing volume action.
683 removeAction(VolumeControlAction.class);
684
685 HdmiCecDeviceInfo avr = getAvrDeviceInfo();
686 addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(),
687 cecVolume, delta > 0));
688 }
689
690 @ServiceThreadOnly
691 void changeMute(boolean mute) {
692 assertRunOnServiceThread();
693 if (!isSystemAudioOn()) {
694 return;
695 }
696
697 // Remove existing volume action.
698 removeAction(VolumeControlAction.class);
699 HdmiCecDeviceInfo avr = getAvrDeviceInfo();
700 addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute));
701 }
702
703 private boolean isSystemAudioOn() {
704 if (getAvrDeviceInfo() == null) {
705 return false;
706 }
707
708 synchronized (mLock) {
709 return mSystemAudioMode;
710 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900711 }
712
713 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900714 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900715 protected boolean handleInitiateArc(HdmiCecMessage message) {
716 assertRunOnServiceThread();
717 // In case where <Initiate Arc> is started by <Request ARC Initiation>
718 // need to clean up RequestArcInitiationAction.
719 removeAction(RequestArcInitiationAction.class);
720 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
721 message.getSource(), true);
722 addAndStartAction(action);
723 return true;
724 }
725
726 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900727 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900728 protected boolean handleTerminateArc(HdmiCecMessage message) {
729 assertRunOnServiceThread();
730 // In case where <Terminate Arc> is started by <Request ARC Termination>
731 // need to clean up RequestArcInitiationAction.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900732 removeAction(RequestArcTerminationAction.class);
733 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
734 message.getSource(), false);
735 addAndStartAction(action);
736 return true;
737 }
738
739 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900740 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900741 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
742 assertRunOnServiceThread();
743 if (!isMessageForSystemAudio(message)) {
744 return false;
745 }
746 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
Jungshik Jang7f0a1c52014-06-23 16:00:07 +0900747 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900748 addAndStartAction(action);
749 return true;
750 }
751
752 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900753 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900754 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
755 assertRunOnServiceThread();
756 if (!isMessageForSystemAudio(message)) {
757 return false;
758 }
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900759 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900760 return true;
761 }
762
763 private boolean isMessageForSystemAudio(HdmiCecMessage message) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900764 if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM
765 || message.getDestination() != Constants.ADDR_TV
Jungshik Jang79c58a42014-06-16 16:45:36 +0900766 || getAvrDeviceInfo() == null) {
767 Slog.w(TAG, "Skip abnormal CecMessage: " + message);
768 return false;
769 }
770 return true;
771 }
772
773 /**
774 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
775 * logical address as new device info's.
776 *
777 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
778 *
779 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added.
780 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo}
781 * that has the same logical address as new one has.
782 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900783 @ServiceThreadOnly
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900784 private HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900785 assertRunOnServiceThread();
786 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
787 if (oldDeviceInfo != null) {
788 removeDeviceInfo(deviceInfo.getLogicalAddress());
789 }
790 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo);
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900791 updateSafeDeviceInfoList();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900792 return oldDeviceInfo;
793 }
794
795 /**
796 * Remove a device info corresponding to the given {@code logicalAddress}.
797 * It returns removed {@link HdmiCecDeviceInfo} if exists.
798 *
799 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
800 *
801 * @param logicalAddress logical address of device to be removed
802 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
803 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900804 @ServiceThreadOnly
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900805 private HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900806 assertRunOnServiceThread();
807 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
808 if (deviceInfo != null) {
809 mDeviceInfos.remove(logicalAddress);
810 }
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900811 updateSafeDeviceInfoList();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900812 return deviceInfo;
813 }
814
815 /**
816 * Return a list of all {@link HdmiCecDeviceInfo}.
817 *
818 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900819 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900820 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900821 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900822 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) {
823 assertRunOnServiceThread();
824 if (includelLocalDevice) {
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900825 return HdmiUtils.sparseArrayToList(mDeviceInfos);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900826 } else {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900827 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
828 for (int i = 0; i < mDeviceInfos.size(); ++i) {
829 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
830 if (!isLocalDeviceAddress(info.getLogicalAddress())) {
831 infoList.add(info);
832 }
833 }
834 return infoList;
835 }
836 }
837
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900838 /**
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900839 * Return external input devices.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900840 */
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900841 List<HdmiCecDeviceInfo> getSafeExternalInputs() {
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900842 synchronized (mLock) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900843 return mSafeExternalInputs;
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900844 }
845 }
846
Jungshik Janga5b74142014-06-23 18:03:10 +0900847 @ServiceThreadOnly
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900848 private void updateSafeDeviceInfoList() {
849 assertRunOnServiceThread();
850 List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900851 List<HdmiCecDeviceInfo> externalInputs = getInputDevices();
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900852 synchronized (mLock) {
853 mSafeAllDeviceInfos = copiedDevices;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900854 mSafeExternalInputs = externalInputs;
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900855 }
856 }
857
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +0900858 /**
859 * Return a list of external cec input (source) devices.
860 *
861 * <p>Note that this effectively excludes non-source devices like system audio,
862 * secondary TV.
863 */
864 private List<HdmiCecDeviceInfo> getInputDevices() {
865 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>();
866 for (int i = 0; i < mDeviceInfos.size(); ++i) {
867 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i);
868 if (isLocalDeviceAddress(i)) {
869 continue;
870 }
871 if (info.isSourceType()) {
872 infoList.add(info);
873 }
874 }
875 return infoList;
876 }
877
Jungshik Janga5b74142014-06-23 18:03:10 +0900878 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900879 private boolean isLocalDeviceAddress(int address) {
880 assertRunOnServiceThread();
881 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
882 if (device.isAddressOf(address)) {
883 return true;
884 }
885 }
886 return false;
887 }
888
Jungshik Jange9cf1582014-06-23 17:28:58 +0900889 @ServiceThreadOnly
890 HdmiCecDeviceInfo getAvrDeviceInfo() {
891 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900892 return getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
Jungshik Jange9cf1582014-06-23 17:28:58 +0900893 }
894
Jungshik Jang79c58a42014-06-16 16:45:36 +0900895 /**
896 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}.
897 *
898 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900899 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900900 *
901 * @param logicalAddress logical address to be retrieved
902 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
903 * Returns null if no logical address matched
904 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900905 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900906 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
907 assertRunOnServiceThread();
908 return mDeviceInfos.get(logicalAddress);
909 }
910
Jungshik Jange9cf1582014-06-23 17:28:58 +0900911 boolean hasSystemAudioDevice() {
912 return getSafeAvrDeviceInfo() != null;
913 }
914
915 HdmiCecDeviceInfo getSafeAvrDeviceInfo() {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900916 return getSafeDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900917 }
918
919 /**
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900920 * Thread safe version of {@link #getDeviceInfo(int)}.
921 *
922 * @param logicalAddress logical address to be retrieved
923 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}.
924 * Returns null if no logical address matched
925 */
926 HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) {
927 synchronized (mLock) {
928 return mSafeAllDeviceInfos.get(logicalAddress);
929 }
930 }
931
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900932 /**
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900933 * Called when a device is newly added or a new device is detected or
934 * existing device is updated.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900935 *
936 * @param info device info of a new device.
937 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900938 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900939 final void addCecDevice(HdmiCecDeviceInfo info) {
940 assertRunOnServiceThread();
941 addDeviceInfo(info);
Jinsuk Kim13c030e2014-06-20 13:25:17 +0900942 if (info.getLogicalAddress() == mAddress) {
943 // The addition of TV device itself should not be notified.
944 return;
945 }
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900946 mService.invokeDeviceEventListeners(info, true);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900947 }
948
949 /**
950 * Called when a device is removed or removal of device is detected.
951 *
952 * @param address a logical address of a device to be removed
953 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900954 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900955 final void removeCecDevice(int address) {
956 assertRunOnServiceThread();
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900957 HdmiCecDeviceInfo info = removeDeviceInfo(address);
Jungshik Jang26dc71e2014-07-04 10:53:27 +0900958
Jungshik Jang79c58a42014-06-16 16:45:36 +0900959 mCecMessageCache.flushMessagesFrom(address);
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900960 mService.invokeDeviceEventListeners(info, false);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900961 }
962
Jungshik Jang26dc71e2014-07-04 10:53:27 +0900963 @ServiceThreadOnly
964 void handleRemoveActiveRoutingPath(int path) {
965 assertRunOnServiceThread();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900966 // Seq #23
967 if (isTailOfActivePath(path, getActivePath())) {
968 removeAction(RoutingControlAction.class);
969 int newPath = mService.portIdToPath(getActivePortId());
970 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
971 mAddress, getActivePath(), newPath));
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900972 addAndStartAction(new RoutingControlAction(this, getActivePortId(), true, null));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900973 }
974 }
975
Jinsuk Kim5344cd92014-07-03 18:02:01 +0900976 /**
977 * Launch routing control process.
978 *
979 * @param routingForBootup true if routing control is initiated due to One Touch Play
980 * or TV power on
981 */
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900982 @ServiceThreadOnly
Jinsuk Kim5344cd92014-07-03 18:02:01 +0900983 void launchRoutingControl(boolean routingForBootup) {
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900984 assertRunOnServiceThread();
985 // Seq #24
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900986 if (getActivePortId() != Constants.INVALID_PORT_ID) {
Jinsuk Kim5344cd92014-07-03 18:02:01 +0900987 if (!routingForBootup && !isProhibitMode()) {
988 removeAction(RoutingControlAction.class);
989 int newPath = mService.portIdToPath(getActivePortId());
990 setActivePath(newPath);
991 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress,
992 getActivePath(), newPath));
993 addAndStartAction(new RoutingControlAction(this, getActivePortId(),
994 routingForBootup, null));
995 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900996 } else {
997 int activePath = mService.getPhysicalAddress();
998 setActivePath(activePath);
Jinsuk Kim5344cd92014-07-03 18:02:01 +0900999 if (!routingForBootup) {
1000 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1001 activePath));
1002 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001003 }
1004 }
1005
Jungshik Jang79c58a42014-06-16 16:45:36 +09001006 /**
1007 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches
1008 * the given routing path. CEC devices use routing path for its physical address to
1009 * describe the hierarchy of the devices in the network.
1010 *
1011 * @param path routing path or physical address
1012 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null
1013 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001014 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001015 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) {
1016 assertRunOnServiceThread();
1017 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) {
1018 if (info.getPhysicalAddress() == path) {
1019 return info;
1020 }
1021 }
1022 return null;
1023 }
1024
1025 /**
1026 * Whether a device of the specified physical address and logical address exists
1027 * in a device info list. However, both are minimal condition and it could
1028 * be different device from the original one.
1029 *
Jungshik Jang79c58a42014-06-16 16:45:36 +09001030 * @param logicalAddress logical address of a device to be searched
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001031 * @param physicalAddress physical address of a device to be searched
Jungshik Jang79c58a42014-06-16 16:45:36 +09001032 * @return true if exist; otherwise false
1033 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001034 @ServiceThreadOnly
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001035 boolean isInDeviceList(int logicalAddress, int physicalAddress) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001036 assertRunOnServiceThread();
1037 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress);
1038 if (device == null) {
1039 return false;
1040 }
1041 return device.getPhysicalAddress() == physicalAddress;
1042 }
1043
1044 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +09001045 @ServiceThreadOnly
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001046 void onHotplug(int portId, boolean connected) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001047 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +09001048
1049 // Tv device will have permanent HotplugDetectionAction.
1050 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1051 if (!hotplugActions.isEmpty()) {
1052 // Note that hotplug action is single action running on a machine.
1053 // "pollAllDevicesNow" cleans up timer and start poll action immediately.
Jungshik Jang24c23c12014-07-07 18:04:39 +09001054 // It covers seq #40, #43.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001055 hotplugActions.get(0).pollAllDevicesNow();
1056 }
Jungshik Jang60cffce2014-06-12 18:03:04 +09001057 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001058
1059 @ServiceThreadOnly
1060 void setAutoDeviceOff(boolean enabled) {
1061 assertRunOnServiceThread();
1062 mAutoDeviceOff = enabled;
1063 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001064
1065 @Override
1066 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001067 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1068 super.disableDevice(initiatedByCec, callback);
Yuncheol Heo38db6292014-07-01 14:15:14 +09001069 assertRunOnServiceThread();
1070 // Remove any repeated working actions.
1071 // HotplugDetectionAction will be reinstated during the wake up process.
1072 // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1073 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001074 removeAction(DeviceDiscoveryAction.class);
Yuncheol Heo38db6292014-07-01 14:15:14 +09001075 removeAction(HotplugDetectionAction.class);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001076
1077 disableSystemAudioIfExist();
1078 disableArcIfExist();
Yuncheol Heo38db6292014-07-01 14:15:14 +09001079 checkIfPendingActionsCleared();
1080 }
1081
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001082 @ServiceThreadOnly
1083 private void disableSystemAudioIfExist() {
1084 assertRunOnServiceThread();
1085 if (getAvrDeviceInfo() == null) {
1086 return;
1087 }
1088
1089 // Seq #31.
1090 removeAction(SystemAudioActionFromAvr.class);
1091 removeAction(SystemAudioActionFromTv.class);
1092 removeAction(SystemAudioAutoInitiationAction.class);
1093 removeAction(SystemAudioStatusAction.class);
1094 removeAction(VolumeControlAction.class);
1095
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +09001096 // Turn off the mode but do not write it the settings, so that the next time TV powers on
1097 // the system audio mode setting can be restored automatically.
1098 setSystemAudioMode(false, false);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001099 }
1100
1101 @ServiceThreadOnly
1102 private void disableArcIfExist() {
1103 assertRunOnServiceThread();
1104 HdmiCecDeviceInfo avr = getAvrDeviceInfo();
1105 if (avr == null) {
1106 return;
1107 }
1108
1109 // Seq #44.
1110 removeAction(RequestArcInitiationAction.class);
1111 if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
1112 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1113 }
1114 }
1115
Yuncheol Heo38db6292014-07-01 14:15:14 +09001116 @Override
1117 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001118 protected void onStandby(boolean initiatedByCec) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001119 assertRunOnServiceThread();
1120 // Seq #11
1121 if (!mService.isControlEnabled()) {
1122 return;
1123 }
1124 if (!initiatedByCec) {
1125 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001126 mAddress, Constants.ADDR_BROADCAST));
Yuncheol Heo38db6292014-07-01 14:15:14 +09001127 }
1128 }
1129
1130 @Override
1131 @ServiceThreadOnly
1132 protected boolean handleStandby(HdmiCecMessage message) {
1133 assertRunOnServiceThread();
1134 // Seq #12
1135 // Tv accepts directly addressed <Standby> only.
1136 if (message.getDestination() == mAddress) {
1137 super.handleStandby(message);
1138 }
1139 return false;
1140 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001141
1142 boolean isProhibitMode() {
1143 return mService.isProhibitMode();
1144 }
Jinsuk Kimb38cd682014-07-07 08:05:03 +09001145
1146 boolean isPowerStandbyOrTransient() {
1147 return mService.isPowerStandbyOrTransient();
Jungshik Jang8866c812014-07-08 14:42:28 +09001148 }
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +09001149
1150 void displayOsd(int messageId) {
1151 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
1152 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
1153 mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
1154 HdmiControlService.PERMISSION);
Jinsuk Kimb38cd682014-07-07 08:05:03 +09001155 }
Jinsuk Kim2918e9e2014-05-20 16:45:45 +09001156}