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