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 android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
20 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
21 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
22 
23 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
24 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
25 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
26 import static com.android.server.hdmi.Constants.ADDR_TV;
27 import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_DEVICE_POWER_ON;
28 import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_REPORT_POWER_STATUS;
29 
30 import static com.google.common.truth.Truth.assertThat;
31 
32 import android.content.Context;
33 import android.hardware.hdmi.HdmiControlManager;
34 import android.hardware.hdmi.HdmiDeviceInfo;
35 import android.hardware.hdmi.HdmiPortInfo;
36 import android.hardware.hdmi.IHdmiControlCallback;
37 import android.os.Looper;
38 import android.os.test.TestLooper;
39 import android.platform.test.annotations.Presubmit;
40 
41 import androidx.test.InstrumentationRegistry;
42 import androidx.test.filters.SmallTest;
43 
44 import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
45 
46 import org.junit.Before;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 import org.junit.runners.JUnit4;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 
54 @SmallTest
55 @Presubmit
56 @RunWith(JUnit4.class)
57 public class DeviceSelectActionFromTvTest {
58 
59     private static final int PORT_1 = 1;
60     private static final int PORT_2 = 1;
61     private static final int PHYSICAL_ADDRESS_PLAYBACK_1 = 0x1000;
62     private static final int PHYSICAL_ADDRESS_PLAYBACK_2 = 0x2000;
63 
64     private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON };
65     private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY };
66     private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON };
67     private static final HdmiCecMessage REPORT_POWER_STATUS_ON = HdmiCecMessage.build(
68             ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON);
69     private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = HdmiCecMessage.build(
70             ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY);
71     private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = HdmiCecMessage.build(
72             ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON);
73     private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath(
74                         ADDR_TV, PHYSICAL_ADDRESS_PLAYBACK_1);
75     private static final HdmiDeviceInfo INFO_PLAYBACK_1 = HdmiDeviceInfo.cecDeviceBuilder()
76             .setLogicalAddress(ADDR_PLAYBACK_1)
77             .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1)
78             .setPortId(PORT_1)
79             .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
80             .setVendorId(0x1234)
81             .setDisplayName("Plyback 1")
82             .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
83             .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
84             .build();
85     private static final HdmiDeviceInfo INFO_PLAYBACK_2 = HdmiDeviceInfo.cecDeviceBuilder()
86             .setLogicalAddress(ADDR_PLAYBACK_2)
87             .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2)
88             .setPortId(PORT_2)
89             .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
90             .setVendorId(0x1234)
91             .setDisplayName("Playback 2")
92             .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
93             .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
94             .build();
95 
96     private HdmiControlService mHdmiControlService;
97     private HdmiCecController mHdmiCecController;
98     private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv;
99     private FakeNativeWrapper mNativeWrapper;
100     private FakePowerManagerWrapper mPowerManager;
101     private Looper mMyLooper;
102     private TestLooper mTestLooper = new TestLooper();
103 
104     @Before
setUp()105     public void setUp() {
106         Context context = InstrumentationRegistry.getTargetContext();
107         mMyLooper = mTestLooper.getLooper();
108 
109         FakeAudioFramework audioFramework = new FakeAudioFramework();
110 
111         mHdmiControlService =
112                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
113                         Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
114                         audioFramework.getAudioManager(),
115                         audioFramework.getAudioDeviceVolumeManager()) {
116                     @Override
117                     boolean isCecControlEnabled() {
118                         return true;
119                     }
120 
121                     @Override
122                     protected void writeStringSystemProperty(String key, String value) {
123                         // do nothing
124                     }
125 
126                     @Override
127                     boolean isPowerStandbyOrTransient() {
128                         return false;
129                     }
130                 };
131 
132 
133         mHdmiControlService.setIoLooper(mMyLooper);
134         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
135         mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
136         mNativeWrapper = new FakeNativeWrapper();
137         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
138                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
139         mHdmiControlService.setCecController(mHdmiCecController);
140         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
141         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
142         hdmiPortInfos[0] =
143                 new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_1)
144                         .setCecSupported(true)
145                         .setMhlSupported(false)
146                         .setArcSupported(false)
147                         .build();
148         hdmiPortInfos[1] =
149                 new HdmiPortInfo.Builder(2, HdmiPortInfo.PORT_INPUT, PHYSICAL_ADDRESS_PLAYBACK_2)
150                         .setCecSupported(true)
151                         .setMhlSupported(false)
152                         .setArcSupported(false)
153                         .build();
154         mNativeWrapper.setPortInfo(hdmiPortInfos);
155         mHdmiControlService.initService();
156         mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
157         mPowerManager = new FakePowerManagerWrapper(context);
158         mHdmiControlService.setPowerManager(mPowerManager);
159         mNativeWrapper.setPhysicalAddress(0x0000);
160         mTestLooper.dispatchAll();
161         mNativeWrapper.clearResultMessages();
162         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_1);
163         mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_PLAYBACK_2);
164         mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
165     }
166 
167     private static class TestActionTimer implements ActionTimer {
168         private int mState;
169 
170         @Override
sendTimerMessage(int state, long delayMillis)171         public void sendTimerMessage(int state, long delayMillis) {
172             mState = state;
173         }
174 
175         @Override
clearTimerMessage()176         public void clearTimerMessage() {
177         }
178 
getState()179         private int getState() {
180             return mState;
181         }
182     }
183 
184     private static class TestCallback extends IHdmiControlCallback.Stub {
185         private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
186 
187         @Override
onComplete(int result)188         public void onComplete(int result) {
189             mCallbackResult.add(result);
190         }
191 
getResult()192         private int getResult() {
193             assertThat(mCallbackResult.size()).isEqualTo(1);
194             return mCallbackResult.get(0);
195         }
196     }
197 
createDeviceSelectAction(TestActionTimer actionTimer, TestCallback callback, boolean isCec20)198     private DeviceSelectActionFromTv createDeviceSelectAction(TestActionTimer actionTimer,
199                                                         TestCallback callback,
200                                                         boolean isCec20) {
201         HdmiDeviceInfo hdmiDeviceInfo =
202                 mHdmiControlService.getHdmiCecNetwork().getCecDeviceInfo(ADDR_PLAYBACK_1);
203         DeviceSelectActionFromTv action = new DeviceSelectActionFromTv(mHdmiCecLocalDeviceTv,
204                                                            hdmiDeviceInfo, callback, isCec20);
205         action.setActionTimer(actionTimer);
206         return action;
207     }
208 
209     @Test
testDeviceSelect_DeviceInPowerOnStatus_Cec14b()210     public void testDeviceSelect_DeviceInPowerOnStatus_Cec14b() {
211         // TV was watching playback2 device connected at port 2, and wants to select
212         // playback1.
213         TestActionTimer actionTimer = new TestActionTimer();
214         TestCallback callback = new TestCallback();
215         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
216                                         /*isCec20=*/false);
217         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
218                                                  "testDeviceSelect");
219         action.start();
220         mTestLooper.dispatchAll();
221         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
222         mNativeWrapper.clearResultMessages();
223         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
224         action.processCommand(REPORT_POWER_STATUS_ON);
225         mTestLooper.dispatchAll();
226         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
227         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
228     }
229 
230     @Test
testDeviceSelect_DeviceInStandbyStatus_Cec14b()231     public void testDeviceSelect_DeviceInStandbyStatus_Cec14b() {
232         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
233                                                  "testDeviceSelect");
234         TestActionTimer actionTimer = new TestActionTimer();
235         TestCallback callback = new TestCallback();
236         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
237                                         /*isCec20=*/false);
238         action.start();
239         mTestLooper.dispatchAll();
240         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
241         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
242         action.processCommand(REPORT_POWER_STATUS_STANDBY);
243         mTestLooper.dispatchAll();
244         HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
245                         ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER);
246         assertThat(mNativeWrapper.getResultMessages()).contains(userControlPressed);
247         mNativeWrapper.clearResultMessages();
248         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
249         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
250         action.processCommand(REPORT_POWER_STATUS_ON);
251         mTestLooper.dispatchAll();
252         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
253         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
254     }
255 
256     @Test
testDeviceSelect_DeviceInStandbyStatusWithSomeTimeouts_Cec14b()257     public void testDeviceSelect_DeviceInStandbyStatusWithSomeTimeouts_Cec14b() {
258         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
259                                                  "testDeviceSelect");
260         TestActionTimer actionTimer = new TestActionTimer();
261         TestCallback callback = new TestCallback();
262         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
263                                         /*isCec20=*/false);
264         action.start();
265         mTestLooper.dispatchAll();
266         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
267         mNativeWrapper.clearResultMessages();
268         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
269         action.processCommand(REPORT_POWER_STATUS_STANDBY);
270         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
271         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
272         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
273         action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON);
274         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
275         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
276         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
277         action.processCommand(REPORT_POWER_STATUS_ON);
278         mTestLooper.dispatchAll();
279         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
280         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
281     }
282 
283     @Test
testDeviceSelect_DeviceInStandbyAfterTimeoutForReportPowerStatus_Cec14b()284     public void testDeviceSelect_DeviceInStandbyAfterTimeoutForReportPowerStatus_Cec14b() {
285         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
286                                                  "testDeviceSelect");
287         TestActionTimer actionTimer = new TestActionTimer();
288         TestCallback callback = new TestCallback();
289         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
290                                         /*isCec20=*/false);
291         action.start();
292         mTestLooper.dispatchAll();
293         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
294         mNativeWrapper.clearResultMessages();
295         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
296         action.processCommand(REPORT_POWER_STATUS_STANDBY);
297         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
298         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
299         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
300         action.processCommand(REPORT_POWER_STATUS_TRANSIENT_TO_ON);
301         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
302         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
303         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
304         action.handleTimerEvent(STATE_WAIT_FOR_REPORT_POWER_STATUS);
305         // Give up getting power status, and just send <Set Stream Path>
306         mTestLooper.dispatchAll();
307         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
308         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
309     }
310 
311     @Test
testDeviceSelect_DeviceInPowerOnStatus_Cec20()312     public void testDeviceSelect_DeviceInPowerOnStatus_Cec20() {
313         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_1,
314                 HdmiControlManager.POWER_STATUS_ON);
315         TestActionTimer actionTimer = new TestActionTimer();
316         TestCallback callback = new TestCallback();
317         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
318                                         /*isCec20=*/true);
319         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
320                                                  "testDeviceSelect");
321         action.start();
322         mTestLooper.dispatchAll();
323         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
324         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
325     }
326 
327     @Test
testDeviceSelect_DeviceInPowerUnknownStatus_Cec20()328     public void testDeviceSelect_DeviceInPowerUnknownStatus_Cec20() {
329         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_1,
330                 HdmiControlManager.POWER_STATUS_UNKNOWN);
331         TestActionTimer actionTimer = new TestActionTimer();
332         TestCallback callback = new TestCallback();
333         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
334                                         /*isCec20=*/true);
335         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
336                                                  "testDeviceSelect");
337         action.start();
338         mTestLooper.dispatchAll();
339         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
340         mNativeWrapper.clearResultMessages();
341         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
342         action.processCommand(REPORT_POWER_STATUS_ON);
343         mTestLooper.dispatchAll();
344         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH);
345         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
346     }
347 
348     @Test
testDeviceSelect_DeviceInStandbyStatus_Cec20()349     public void testDeviceSelect_DeviceInStandbyStatus_Cec20() {
350         mHdmiControlService.getHdmiCecNetwork().updateDevicePowerStatus(ADDR_PLAYBACK_1,
351                 HdmiControlManager.POWER_STATUS_STANDBY);
352         mHdmiCecLocalDeviceTv.updateActiveSource(ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2,
353                                                  "testDeviceSelect");
354         TestActionTimer actionTimer = new TestActionTimer();
355         TestCallback callback = new TestCallback();
356         DeviceSelectActionFromTv action = createDeviceSelectAction(actionTimer, callback,
357                                         /*isCec20=*/true);
358         action.start();
359         mTestLooper.dispatchAll();
360         assertThat(mNativeWrapper.getResultMessages()).contains(SET_STREAM_PATH);
361         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_REPORT_POWER_STATUS);
362         action.processCommand(REPORT_POWER_STATUS_STANDBY);
363         mTestLooper.dispatchAll();
364         HdmiCecMessage userControlPressed = HdmiCecMessageBuilder.buildUserControlPressed(
365                         ADDR_TV, ADDR_PLAYBACK_1, HdmiCecKeycode.CEC_KEYCODE_POWER);
366         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(userControlPressed);
367         mNativeWrapper.clearResultMessages();
368         assertThat(actionTimer.getState()).isEqualTo(STATE_WAIT_FOR_DEVICE_POWER_ON);
369         action.handleTimerEvent(STATE_WAIT_FOR_DEVICE_POWER_ON);
370         action.processCommand(REPORT_POWER_STATUS_ON);
371         mTestLooper.dispatchAll();
372         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(SET_STREAM_PATH);
373         assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
374     }
375 }
376