1 /*
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 
17 package com.android.server.hdmi;
18 
19 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
20 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
21 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
22 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
23 import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
24 import static com.android.server.hdmi.Constants.ADDR_TV;
25 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
26 import static com.android.server.hdmi.Constants.MESSAGE_ACTIVE_SOURCE;
27 import static com.android.server.hdmi.Constants.MESSAGE_ROUTING_INFORMATION;
28 import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTING_INFORMATION;
29 
30 import static com.google.common.truth.Truth.assertThat;
31 
32 import android.content.Context;
33 import android.hardware.hdmi.HdmiDeviceInfo;
34 import android.hardware.hdmi.HdmiPortInfo;
35 import android.hardware.hdmi.IHdmiControlCallback;
36 import android.os.Looper;
37 import android.os.test.TestLooper;
38 import android.platform.test.annotations.Presubmit;
39 
40 import androidx.test.InstrumentationRegistry;
41 import androidx.test.filters.SmallTest;
42 
43 import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
44 
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.junit.runners.JUnit4;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.List;
53 
54 @SmallTest
55 @Presubmit
56 @RunWith(JUnit4.class)
57 public class RoutingControlActionTest {
58     /*
59      * Example connection diagram used in tests. Double-lined paths indicate the currently active
60      * routes.
61      *
62      *
63      *                              +-----------+
64      *                              |    TV     |
65      *                              |  0.0.0.0  |
66      *                              +---+-----+-+
67      *                                  |     |
68      *                               <----------+ 1) AVR -> Switch
69      *             +----------+         |     |  +-----------+
70      *             | AVR      +---------+     +--+ Switch    |
71      *             | 1.0.0.0  |                  | 2.0.0.0   |
72      *             +--+---++--+                  +--++-----+-+  <-------+ 2) Recorder -> Blu-ray
73      *                |   ||                        ||     |
74      *                |   ||                        ||     +--------+
75      * +-----------+  |   ||  +----------+     +----++----+         |
76      * | XBox      +--+   ++--+ Tuner    |     | Blueray  |   +-----+----+
77      * | 1.1.0.0   |          | 1.2.0.0  |     | 2.1.0.0  |   | Recorder |
78      * +-----------+          +----++----+     +----------+   | 2.2.0.0  |
79      *                             ||                         +----------+
80      *                             ||
81      *                        +----++----+
82      *                        | Player   |
83      *                        | 1.2.1.0  |
84      *                        +----------+
85      *
86      */
87 
88     private static final int PHYSICAL_ADDRESS_TV = 0x0000;
89     private static final int PHYSICAL_ADDRESS_AVR = 0x1000;
90     private static final int PHYSICAL_ADDRESS_SWITCH = 0x2000;
91     private static final int PHYSICAL_ADDRESS_TUNER = 0x1200;
92     private static final int PHYSICAL_ADDRESS_PLAYER = 0x1210;
93     private static final int PHYSICAL_ADDRESS_BLUERAY = 0x2100;
94     private static final int PHYSICAL_ADDRESS_RECORDER = 0x2200;
95     private static final int PORT_1 = 1;
96     private static final int PORT_2 = 2;
97     private static final int VENDOR_ID_AVR = 0x11233;
98 
99     private static final byte[] TUNER_PARAM =
100             new byte[]{(PHYSICAL_ADDRESS_TUNER >> 8) & 0xFF, PHYSICAL_ADDRESS_TUNER & 0xFF};
101     private static final byte[] PLAYER_PARAM =
102             new byte[]{(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF};
103 
104     private static final HdmiDeviceInfo DEVICE_INFO_AVR = HdmiDeviceInfo.cecDeviceBuilder()
105             .setLogicalAddress(ADDR_AUDIO_SYSTEM)
106             .setPhysicalAddress(PHYSICAL_ADDRESS_AVR)
107             .setPortId(PORT_1)
108             .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
109             .setVendorId(VENDOR_ID_AVR)
110             .setDisplayName("Audio")
111             .build();
112     private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = HdmiDeviceInfo.cecDeviceBuilder()
113             .setLogicalAddress(ADDR_PLAYBACK_1)
114             .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYER)
115             .setPortId(PORT_1)
116             .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
117             .setVendorId(VENDOR_ID_AVR)
118             .setDisplayName("Player")
119             .build();
120     private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = HdmiCecMessage.build(
121             ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM);
122     private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = HdmiCecMessage.build(
123             ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM);
124     private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = HdmiCecMessage.build(
125             ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM);
126     private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = HdmiCecMessage.build(
127             ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM);
128 
129     private HdmiControlService mHdmiControlService;
130     private HdmiCecController mHdmiCecController;
131     private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
132     private FakeNativeWrapper mNativeWrapper;
133     private FakePowerManagerWrapper mPowerManager;
134     private Looper mMyLooper;
135     private TestLooper mTestLooper = new TestLooper();
136 
createRoutingControlAction(HdmiCecLocalDeviceTv localDevice, TestInputSelectCallback callback)137     private static RoutingControlAction createRoutingControlAction(HdmiCecLocalDeviceTv localDevice,
138             TestInputSelectCallback callback) {
139         return new RoutingControlAction(localDevice, PHYSICAL_ADDRESS_AVR, callback);
140     }
141 
142     @Before
setUp()143     public void setUp() {
144         Context context = InstrumentationRegistry.getTargetContext();
145         mMyLooper = mTestLooper.getLooper();
146 
147         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(context);
148 
149         FakeAudioFramework audioFramework = new FakeAudioFramework();
150 
151         mHdmiControlService =
152                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
153                         Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
154                         audioFramework.getAudioManager(),
155                         audioFramework.getAudioDeviceVolumeManager()) {
156                     @Override
157                     boolean isCecControlEnabled() {
158                         return true;
159                     }
160 
161                     @Override
162                     protected void writeStringSystemProperty(String key, String value) {
163                         // do nothing
164                     }
165 
166                     @Override
167                     boolean isPowerStandbyOrTransient() {
168                         return false;
169                     }
170 
171                     @Override
172                     protected HdmiCecConfig getHdmiCecConfig() {
173                         return hdmiCecConfig;
174                     }
175                 };
176 
177         mHdmiControlService.setIoLooper(mMyLooper);
178         mNativeWrapper = new FakeNativeWrapper();
179         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
180                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
181         mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
182         mHdmiControlService.setCecController(mHdmiCecController);
183         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
184         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
185         hdmiPortInfos[0] =
186                 new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_AVR)
187                         .setCecSupported(true)
188                         .setMhlSupported(false)
189                         .setArcSupported(false)
190                         .build();
191         mNativeWrapper.setPortInfo(hdmiPortInfos);
192         mHdmiControlService.initService();
193         mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
194         mPowerManager = new FakePowerManagerWrapper(context);
195         mHdmiControlService.setPowerManager(mPowerManager);
196         mNativeWrapper.setPhysicalAddress(0x0000);
197         mTestLooper.dispatchAll();
198         mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
199         mNativeWrapper.clearResultMessages();
200         mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
201     }
202 
203     // Routing control succeeds against the device connected directly to the port. Action
204     // won't get any <Routing Information> in this case. It times out on <Routing Information>,
205     // regards the directly connected one as the new routing path to switch to.
206     @Test
testRoutingControl_succeedForDirectlyConnectedDevice()207     public void testRoutingControl_succeedForDirectlyConnectedDevice() {
208         TestInputSelectCallback callback = new TestInputSelectCallback();
209         TestActionTimer actionTimer = new TestActionTimer();
210         mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_AVR);
211 
212         RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback);
213         action.setActionTimer(actionTimer);
214         action.start();
215         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION);
216 
217         action.handleTimerEvent(actionTimer.getState());
218         mTestLooper.dispatchAll();
219         HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
220                 ADDR_TV, PHYSICAL_ADDRESS_AVR);
221         assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
222     }
223 
224     // Succeeds by receiving a couple of <Routing Information> commands, followed by
225     // <Set Stream Path> going out in the end.
226     @Test
testRoutingControl_succeedForDeviceBehindSwitch()227     public void testRoutingControl_succeedForDeviceBehindSwitch() {
228         TestInputSelectCallback callback = new TestInputSelectCallback();
229         TestActionTimer actionTimer = new TestActionTimer();
230         mHdmiControlService.getHdmiCecNetwork().addCecDevice(DEVICE_INFO_PLAYER);
231         RoutingControlAction action = createRoutingControlAction(mHdmiCecLocalDeviceTv, callback);
232         action.setActionTimer(actionTimer);
233         action.start();
234 
235         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_ROUTING_INFORMATION);
236 
237         action.processCommand(ROUTING_INFORMATION_TUNER);
238         action.processCommand(ROUTING_INFORMATION_PLAYER);
239 
240         action.handleTimerEvent(actionTimer.getState());
241         mTestLooper.dispatchAll();
242         HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
243                 ADDR_TV, PHYSICAL_ADDRESS_PLAYER);
244         assertThat(mNativeWrapper.getResultMessages()).contains(setStreamPath);
245     }
246 
247     private static class TestActionTimer implements ActionTimer {
248         private int mState;
249 
250         @Override
sendTimerMessage(int state, long delayMillis)251         public void sendTimerMessage(int state, long delayMillis) {
252             mState = state;
253         }
254 
255         @Override
clearTimerMessage()256         public void clearTimerMessage() {
257         }
258 
getState()259         private int getState() {
260             return mState;
261         }
262     }
263 
264     private static class TestInputSelectCallback extends IHdmiControlCallback.Stub {
265         private final List<Integer> mCallbackResult = new ArrayList<Integer>();
266 
267         @Override
onComplete(int result)268         public void onComplete(int result) {
269             mCallbackResult.add(result);
270         }
271 
getResult()272         private int getResult() {
273             assert (mCallbackResult.size() == 1);
274             return mCallbackResult.get(0);
275         }
276     }
277 }
278