1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tv.tests.ui;
17 
18 import static junit.framework.Assert.assertTrue;
19 
20 import android.app.Instrumentation;
21 import android.app.UiAutomation;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.os.Build;
25 import android.os.SystemClock;
26 import android.support.test.uiautomator.BySelector;
27 import android.support.test.uiautomator.Configurator;
28 import android.support.test.uiautomator.SearchCondition;
29 import android.support.test.uiautomator.UiDevice;
30 import android.support.test.uiautomator.Until;
31 import android.view.InputDevice;
32 import android.view.KeyEvent;
33 import androidx.test.InstrumentationRegistry;
34 import com.android.tv.testing.data.ChannelInfo;
35 import com.android.tv.testing.testinput.ChannelStateData;
36 import com.android.tv.testing.testinput.TestInputControlConnection;
37 import com.android.tv.testing.testinput.TestInputControlUtils;
38 import com.android.tv.testing.uihelper.Constants;
39 import com.android.tv.testing.uihelper.LiveChannelsUiDeviceHelper;
40 import com.android.tv.testing.uihelper.MenuHelper;
41 import com.android.tv.testing.uihelper.SidePanelHelper;
42 import com.android.tv.testing.uihelper.UiDeviceAsserts;
43 import com.android.tv.testing.uihelper.UiDeviceUtils;
44 import org.junit.rules.ExternalResource;
45 
46 /** UIHelpers and TestInputController for LiveChannels. */
47 public class LiveChannelsTestController extends ExternalResource {
48 
49     private final TestInputControlConnection mConnection = new TestInputControlConnection();
50 
51     public MenuHelper menuHelper;
52     public SidePanelHelper sidePanelHelper;
53     public LiveChannelsUiDeviceHelper liveChannelsHelper;
54 
55     private UiDevice mDevice;
56     private Resources mTargetResources;
57     private Instrumentation mInstrumentation;
58 
assertPressKeyUp(int keyCode, boolean sync)59     private void assertPressKeyUp(int keyCode, boolean sync) {
60         assertPressKey(KeyEvent.ACTION_UP, keyCode, sync);
61     }
62 
assertPressKey(int action, int keyCode, boolean sync)63     private void assertPressKey(int action, int keyCode, boolean sync) {
64         long eventTime = SystemClock.uptimeMillis();
65         KeyEvent event =
66                 new KeyEvent(
67                         eventTime,
68                         eventTime,
69                         action,
70                         keyCode,
71                         0,
72                         0,
73                         -1,
74                         0,
75                         0,
76                         InputDevice.SOURCE_KEYBOARD);
77         assertTrue("Failed to inject key up event:" + event, injectEvent(event, sync));
78     }
79 
getUiAutomation()80     private UiAutomation getUiAutomation() {
81         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
82             int flags = Configurator.getInstance().getUiAutomationFlags();
83             return mInstrumentation.getUiAutomation(flags);
84         } else {
85             return mInstrumentation.getUiAutomation();
86         }
87     }
88 
89     @Override
before()90     protected void before() throws Exception {
91         mInstrumentation = InstrumentationRegistry.getInstrumentation();
92         Context context = mInstrumentation.getContext();
93         context.bindService(
94                 TestInputControlUtils.createIntent(), mConnection, Context.BIND_AUTO_CREATE);
95         mDevice = UiDevice.getInstance(mInstrumentation);
96         mTargetResources = mInstrumentation.getTargetContext().getResources();
97         menuHelper = new MenuHelper(mDevice, mTargetResources);
98         sidePanelHelper = new SidePanelHelper(mDevice, mTargetResources);
99         liveChannelsHelper = new LiveChannelsUiDeviceHelper(mDevice, mTargetResources, context);
100     }
101 
102     @Override
after()103     protected void after() {
104         if (mConnection.isBound()) {
105             mInstrumentation.getContext().unbindService(mConnection);
106         }
107 
108         // TODO: robustly handle left over pops from failed tests.
109         // Clear any side panel, menu, ...
110         // Scene container should not be checked here because pressing the BACK key in some scenes
111         // might launch the home screen.
112         if (mDevice.hasObject(Constants.SIDE_PANEL)
113                 || mDevice.hasObject(Constants.MENU)
114                 || mDevice.hasObject(Constants.PROGRAM_GUIDE)) {
115             mDevice.pressBack();
116         }
117         // To destroy the activity to make sure next test case's activity launch check works well.
118         mDevice.pressBack();
119     }
120 
121     /**
122      * Update the channel state to {@code data} then tune to that channel.
123      *
124      * @param data the state to update the channel with.
125      * @param channel the channel to tune to
126      */
updateThenTune(ChannelStateData data, ChannelInfo channel)127     protected void updateThenTune(ChannelStateData data, ChannelInfo channel) {
128         mConnection.updateChannelState(channel, data);
129         pressKeysForChannel(channel);
130     }
131 
132     /**
133      * Send the keys for the channel number of {@code channel} and press the DPAD center.
134      *
135      * <p>Usually this will tune to the given channel.
136      */
pressKeysForChannel(ChannelInfo channel)137     public void pressKeysForChannel(ChannelInfo channel) {
138         UiDeviceUtils.pressKeys(mDevice, channel.number);
139         UiDeviceAsserts.assertWaitForCondition(
140                 mDevice, Until.hasObject(Constants.KEYPAD_CHANNEL_SWITCH));
141         mDevice.pressDPadCenter();
142     }
143 
assertHas(BySelector bySelector, boolean expected)144     public void assertHas(BySelector bySelector, boolean expected) {
145         UiDeviceAsserts.assertHas(mDevice, bySelector, expected);
146     }
147 
assertWaitForCondition(SearchCondition<Boolean> booleanSearchCondition)148     public void assertWaitForCondition(SearchCondition<Boolean> booleanSearchCondition) {
149         UiDeviceAsserts.assertWaitForCondition(mDevice, booleanSearchCondition);
150     }
151 
assertWaitForCondition(SearchCondition<Boolean> gone, long timeout)152     public void assertWaitForCondition(SearchCondition<Boolean> gone, long timeout) {
153         UiDeviceAsserts.assertWaitForCondition(mDevice, gone, timeout);
154     }
155 
assertWaitUntilFocused(BySelector bySelector)156     public void assertWaitUntilFocused(BySelector bySelector) {
157         UiDeviceAsserts.assertWaitUntilFocused(mDevice, bySelector);
158     }
159 
getTargetResources()160     public Resources getTargetResources() {
161         return mTargetResources;
162     }
163 
getUiDevice()164     public UiDevice getUiDevice() {
165         return mDevice;
166     }
167 
injectEvent(KeyEvent event, boolean sync)168     public boolean injectEvent(KeyEvent event, boolean sync) {
169         return getUiAutomation().injectInputEvent(event, sync);
170     }
171 
pressBack()172     public void pressBack() {
173         mDevice.pressBack();
174     }
175 
pressDPadCenter()176     public void pressDPadCenter() {
177         pressDPadCenter(1);
178     }
179 
pressDPadCenter(int repeat)180     public void pressDPadCenter(int repeat) {
181         UiDevice.getInstance(mInstrumentation).waitForIdle();
182         for (int i = 0; i < repeat; ++i) {
183             assertPressKey(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, false);
184             if (i < repeat - 1) {
185                 assertPressKeyUp(KeyEvent.KEYCODE_DPAD_CENTER, false);
186             }
187         }
188         // Send last key event synchronously.
189         assertPressKeyUp(KeyEvent.KEYCODE_DPAD_CENTER, true);
190     }
191 
pressDPadDown()192     public void pressDPadDown() {
193         mDevice.pressDPadDown();
194     }
195 
pressDPadLeft()196     public void pressDPadLeft() {
197         mDevice.pressDPadLeft();
198     }
199 
pressDPadRight()200     public void pressDPadRight() {
201         mDevice.pressDPadRight();
202     }
203 
pressDPadUp()204     public void pressDPadUp() {
205         mDevice.pressDPadUp();
206     }
207 
pressEnter()208     public void pressEnter() {
209         mDevice.pressEnter();
210     }
211 
pressHome()212     public void pressHome() {
213         mDevice.pressHome();
214     }
215 
pressKeyCode(int keyCode)216     public void pressKeyCode(int keyCode) {
217         mDevice.pressKeyCode(keyCode);
218     }
219 
pressMenu()220     public void pressMenu() {
221         mDevice.pressMenu();
222     }
223 
waitForIdle()224     public void waitForIdle() {
225         mDevice.waitForIdle();
226     }
227 
waitForIdleSync()228     public void waitForIdleSync() {
229         mInstrumentation.waitForIdleSync();
230     }
231 }
232