1 /* 2 * Copyright (C) 2016 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 android.platform.test.helpers; 18 19 import android.app.Instrumentation; 20 import android.platform.test.helpers.exceptions.UnknownUiException; 21 import android.support.test.launcherhelper.ILeanbackLauncherStrategy; 22 import android.support.test.launcherhelper.LauncherStrategyFactory; 23 import android.support.test.uiautomator.By; 24 import android.support.test.uiautomator.BySelector; 25 import android.support.test.uiautomator.Direction; 26 import android.support.test.uiautomator.UiObject2; 27 import android.support.test.uiautomator.Until; 28 import android.util.Log; 29 30 /** 31 * This app helper handles the following important widgets for TV apps: 32 * BrowseFragment, DetailsFragment, SearchFragment and PlaybackOverlayFragment 33 */ 34 public abstract class AbstractLeanbackAppHelper extends AbstractStandardAppHelper { 35 36 private static final String TAG = AbstractLeanbackAppHelper.class.getSimpleName(); 37 private static final long OPEN_SECTION_WAIT_TIME_MS = 5000; 38 private static final long OPEN_SIDE_PANEL_WAIT_TIME_MS = 5000; 39 private static final int OPEN_SIDE_PANEL_MAX_ATTEMPTS = 5; 40 private static final long MAIN_ACTIVITY_WAIT_TIME_MS = 250; 41 42 protected DPadHelper mDPadHelper; 43 public ILeanbackLauncherStrategy mLauncherStrategy; 44 45 AbstractLeanbackAppHelper(Instrumentation instr)46 public AbstractLeanbackAppHelper(Instrumentation instr) { 47 super(instr); 48 mDPadHelper = DPadHelper.getInstance(instr); 49 mLauncherStrategy = LauncherStrategyFactory.getInstance( 50 mDevice).getLeanbackLauncherStrategy(); 51 } 52 getAppSelector()53 protected abstract BySelector getAppSelector(); 54 getSidePanelSelector()55 protected abstract BySelector getSidePanelSelector(); 56 getSidePanelResultSelector(String sectionName)57 protected abstract BySelector getSidePanelResultSelector(String sectionName); 58 59 /** 60 * Selector to identify main activity for getMainActivitySelector(). 61 * Not every application has its main activity, so the override is optional. 62 */ getMainActivitySelector()63 protected BySelector getMainActivitySelector() { 64 return null; 65 } 66 67 /** 68 * Setup expectation: Side panel is selected on browse fragment 69 * 70 * Best effort attempt to go to the side panel, and open the selected section. 71 */ openSection(String sectionName)72 public void openSection(String sectionName) { 73 openSidePanel(); 74 // Section header is focused; it should not be after pressing the DPad 75 selectSection(sectionName); 76 mDevice.pressDPadCenter(); 77 78 // Test for focus change and selection result 79 BySelector sectionResult = getSidePanelResultSelector(sectionName); 80 if (!mDevice.wait(Until.hasObject(sectionResult), OPEN_SECTION_WAIT_TIME_MS)) { 81 throw new UnknownUiException( 82 String.format("Failed to find result opening section %s", sectionName)); 83 } 84 Log.v(TAG, "Successfully opened section"); 85 } 86 87 /** 88 * Setup expectation: On navigation screen on browse fragment 89 * 90 * Best effort attempt to open the side panel. 91 * @param onMainActivity True if it opens the side panel on app's main activity. 92 */ openSidePanel(boolean onMainActivity)93 public void openSidePanel(boolean onMainActivity) { 94 if (onMainActivity) { 95 returnToMainActivity(); 96 } 97 int attempts = 0; 98 while (!isSidePanelSelected(OPEN_SIDE_PANEL_WAIT_TIME_MS) 99 && attempts++ < OPEN_SIDE_PANEL_MAX_ATTEMPTS) { 100 mDevice.pressDPadLeft(); 101 } 102 if (attempts == OPEN_SIDE_PANEL_MAX_ATTEMPTS) { 103 throw new UnknownUiException("Failed to open side panel"); 104 } 105 } 106 openSidePanel()107 public void openSidePanel() { 108 openSidePanel(false); 109 } 110 111 /** 112 * Select target item through the container in the given direction. 113 * @param container 114 * @param target 115 * @param direction 116 * @return the focused object 117 */ select(UiObject2 container, BySelector target, Direction direction)118 public UiObject2 select(UiObject2 container, BySelector target, Direction direction) { 119 if (container == null) { 120 throw new IllegalArgumentException("The container should not be null."); 121 } 122 UiObject2 focus = container.findObject(By.focused(true)); 123 if (focus == null) { 124 throw new UnknownUiException("The container should have a focus."); 125 } 126 while (!focus.hasObject(target)) { 127 UiObject2 prev = focus; 128 mDPadHelper.pressDPad(direction); 129 focus = container.findObject(By.focused(true)); 130 if (focus == null) { 131 mDPadHelper.pressDPad(Direction.reverse(direction)); 132 focus = container.findObject(By.focused(true)); 133 } 134 if (focus.equals(prev)) { 135 // It reached at the end, but no target is found. 136 return null; 137 } 138 } 139 return focus; 140 } 141 142 /** 143 * Attempts to return to main activity with getMainActivitySelector() 144 * by pressing the back button repeatedly and sleeping briefly to allow for UI slowness. 145 */ returnToMainActivity()146 public void returnToMainActivity() { 147 int maxBackAttempts = 10; 148 BySelector selector = getMainActivitySelector(); 149 if (selector == null) { 150 throw new IllegalStateException("getMainActivitySelector() should be overridden."); 151 } 152 while (!mDevice.wait(Until.hasObject(selector), MAIN_ACTIVITY_WAIT_TIME_MS) 153 && maxBackAttempts-- > 0) { 154 mDevice.pressBack(); 155 } 156 } 157 158 @Override dismissInitialDialogs()159 public void dismissInitialDialogs() { 160 return; 161 } 162 isSidePanelSelected(long timeout)163 protected boolean isSidePanelSelected(long timeout) { 164 UiObject2 sidePanel = mDevice.wait(Until.findObject(getSidePanelSelector()), timeout); 165 if (sidePanel == null) { 166 return false; 167 } 168 return sidePanel.hasObject(By.focused(true).minDepth(1)); 169 } 170 selectSection(String sectionName)171 protected UiObject2 selectSection(String sectionName) { 172 UiObject2 container = mDevice.wait( 173 Until.findObject(getSidePanelSelector()), OPEN_SIDE_PANEL_WAIT_TIME_MS); 174 BySelector section = By.clazz(".TextView").text(sectionName); 175 176 // Wait until the section text appears at runtime. This needs to be long enough to run under 177 // low bandwidth environments in the test lab. 178 mDevice.wait(Until.findObject(section), 60 * 1000); 179 180 // Search up, then down 181 UiObject2 focused = select(container, section, Direction.UP); 182 if (focused != null) { 183 return focused; 184 } 185 focused = select(container, section, Direction.DOWN); 186 if (focused != null) { 187 return focused; 188 } 189 throw new UnknownUiException("Failed to select section"); 190 } 191 192 } 193