1 /*
2  * Copyright (C) 2020 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.autofillservice.cts.testcore;
18 
19 import static android.autofillservice.cts.testcore.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
20 import static android.autofillservice.cts.testcore.Timeouts.LONG_PRESS_MS;
21 import static android.autofillservice.cts.testcore.Timeouts.UI_TIMEOUT;
22 
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.content.res.Resources;
26 import android.os.SystemClock;
27 import android.util.Log;
28 
29 import androidx.test.uiautomator.By;
30 import androidx.test.uiautomator.BySelector;
31 import androidx.test.uiautomator.Direction;
32 import androidx.test.uiautomator.StaleObjectException;
33 import androidx.test.uiautomator.UiObject2;
34 
35 import com.android.compatibility.common.util.RequiredFeatureRule;
36 import com.android.cts.mockime.MockIme;
37 
38 import org.junit.rules.RuleChain;
39 import org.junit.rules.TestRule;
40 
41 /**
42  * UiBot for the inline suggestion.
43  */
44 public final class InlineUiBot extends UiBot {
45 
46     private static final String TAG = "AutoFillInlineCtsUiBot";
47     public static final String SUGGESTION_STRIP_DESC = "MockIme Inline Suggestion View";
48 
49     private static final BySelector SUGGESTION_STRIP_SELECTOR = By.desc(SUGGESTION_STRIP_DESC);
50 
51     private static final RequiredFeatureRule REQUIRES_IME_RULE = new RequiredFeatureRule(
52             PackageManager.FEATURE_INPUT_METHODS);
53 
54     private final Context mContext;
55 
InlineUiBot(Context context)56     public InlineUiBot(Context context) {
57         super(UI_TIMEOUT);
58         mContext = context;
59     }
60 
annotateRule(TestRule rule)61     public static RuleChain annotateRule(TestRule rule) {
62         return RuleChain.outerRule(REQUIRES_IME_RULE).around(rule);
63     }
64 
65     @Override
assertNoDatasets()66     public void assertNoDatasets() throws Exception {
67         assertNoDatasetsEver();
68     }
69 
70     @Override
assertNoDatasetsEver()71     public void assertNoDatasetsEver() throws Exception {
72         assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
73                 DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
74     }
75 
76     /**
77      * Selects the suggestion in the {@link MockIme}'s suggestion strip by the given text.
78      */
selectSuggestion(String name)79     public void selectSuggestion(String name) throws Exception {
80         final UiObject2 strip = findSuggestionStrip();
81         final UiObject2 dataset = strip.findObject(By.text(name));
82         if (dataset == null) {
83             throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
84         }
85         dataset.click();
86     }
87 
88     @Override
selectDataset(String name)89     public void selectDataset(String name) throws Exception {
90         int retryCount = 0;
91         while (retryCount < MAX_UIOBJECT_RETRY_COUNT) {
92             try {
93                 selectSuggestion(name);
94                 break;
95             } catch (AssertionError ex) {
96                 retryCount++;
97                 if (retryCount >= MAX_UIOBJECT_RETRY_COUNT) {
98                     throw ex;
99                 } else {
100                     SystemClock.sleep(100);
101                 }
102             }
103         }
104     }
105 
106     @Override
longPressSuggestion(String name)107     public void longPressSuggestion(String name) throws Exception {
108         final UiObject2 strip = findSuggestionStrip();
109         final UiObject2 dataset = strip.findObject(By.text(name));
110         if (dataset == null) {
111             throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
112         }
113         dataset.click(LONG_PRESS_MS);
114     }
115 
116     @Override
assertDatasets(String... names)117     public UiObject2 assertDatasets(String... names) throws Exception {
118         UiObject2 picker = null;
119         int retryCount = 0;
120         while (retryCount < MAX_UIOBJECT_RETRY_COUNT) {
121             try {
122                 picker = findSuggestionStrip();
123                 return assertDatasets(picker, names);
124             } catch (StaleObjectException ex) {
125                 retryCount++;
126                 if (retryCount >= MAX_UIOBJECT_RETRY_COUNT) {
127                     throw ex;
128                 } else {
129                     SystemClock.sleep(100);
130                 }
131             }
132         }
133 
134         // Unreachable, either:
135         // 1. returns assertDatasets(picker, names)
136         // 2. StaleObjectException is thrown above
137         return picker;
138     }
139 
140     @Override
assertSuggestion(String name)141     public void assertSuggestion(String name) throws Exception {
142         final UiObject2 strip = findSuggestionStrip();
143         final UiObject2 dataset = strip.findObject(By.text(name));
144         if (dataset == null) {
145             throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
146         }
147     }
148 
149     @Override
assertNoSuggestion(String name)150     public void assertNoSuggestion(String name) throws Exception {
151         final UiObject2 strip = findSuggestionStrip();
152         final UiObject2 dataset = strip.findObject(By.text(name));
153         if (dataset != null) {
154             throw new AssertionError("has dataset " + name + " in " + getChildrenAsText(strip));
155         }
156     }
157 
158     @Override
scrollSuggestionView(Direction direction, int speed)159     public void scrollSuggestionView(Direction direction, int speed) throws Exception {
160         final UiObject2 strip = findSuggestionStrip();
161         final int defaultWidth = strip.getVisibleBounds().width() / 4;
162         final int width = getEdgeSensitivityWidth(defaultWidth);
163         strip.setGestureMargin(width);
164         strip.fling(direction, speed);
165     }
166 
assertTooltipShowing(String text)167     public void assertTooltipShowing(String text) throws Exception {
168         final UiObject2 strip = waitForObject(By.text(text), UI_TIMEOUT);
169         if (strip == null) {
170             throw new AssertionError("not find inline tooltip by text: " + text);
171         }
172     }
173 
findSuggestionStrip()174     private UiObject2 findSuggestionStrip() throws Exception {
175         return waitForObject(SUGGESTION_STRIP_SELECTOR, Timeouts.UI_TIMEOUT);
176     }
177 
getEdgeSensitivityWidth(int defaultWidth)178     private int getEdgeSensitivityWidth(int defaultWidth) {
179         Resources resources = mContext.getResources();
180         int resId = resources.getIdentifier("config_backGestureInset", "dimen", "android");
181         try {
182             int width = resources.getDimensionPixelSize(resId) + 1;
183             if (width >= defaultWidth) {
184                 return width;
185             }
186             Log.i(TAG, "Got edge sensitivity width of " + width
187                     + ", which is less than the default of " + defaultWidth + ". Using default");
188             return defaultWidth;
189         } catch (Resources.NotFoundException e) {
190             Log.e(TAG, "Failed to get edge sensitivity width. Defaulting to " + defaultWidth, e);
191             return defaultWidth;
192         }
193     }
194 }
195