1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "widget_operator.h"
17 
18 namespace OHOS::uitest {
19     using namespace std;
20     using namespace nlohmann;
21 
22     static constexpr float SCROLL_MOVE_FACTOR = 0.7;
23 
IsScrolledToBorder(int oriDis, const std::vector<unique_ptr<Widget>> &allWidgets, const unique_ptr<Widget> &anchorLeafWidget)24     static bool IsScrolledToBorder(int oriDis,
25                                    const std::vector<unique_ptr<Widget>> &allWidgets,
26                                    const unique_ptr<Widget> &anchorLeafWidget)
27     {
28         if (oriDis < 0) {
29             return false;
30         }
31         size_t index = 0;
32         for (; index < allWidgets.size(); ++index) {
33             if (allWidgets.at(index)->GetAttr(UiAttr::ACCESSIBILITY_ID) ==
34                 anchorLeafWidget->GetAttr(UiAttr::ACCESSIBILITY_ID)) {
35                 return std::abs(allWidgets.at(index)->GetBounds().top_ - anchorLeafWidget->GetBounds().top_) <
36                        oriDis * SCROLL_MOVE_FACTOR;
37             }
38         }
39         return false;
40     }
41 
ConstructNoFilterInWidgetSelector(WidgetSelector &scrollSelector, const std::string &hostApp, const std::string &hashCode)42     static void ConstructNoFilterInWidgetSelector(WidgetSelector &scrollSelector,
43                                                   const std::string &hostApp,
44                                                   const std::string &hashCode)
45     {
46         WidgetSelector parentStrategy;
47         WidgetMatchModel anchorModel2{UiAttr::HASHCODE, hashCode, ValueMatchPattern::EQ};
48         parentStrategy.AddMatcher(anchorModel2);
49         scrollSelector.AddAppLocator(hostApp);
50         auto error = ApiCallErr(NO_ERROR);
51         scrollSelector.AddParentLocator(parentStrategy, error);
52         scrollSelector.SetWantMulti(true);
53     }
54 
CalcFirstLeafWithInScroll(const std::vector<unique_ptr<Widget>>& widgetsInScroll)55     static int CalcFirstLeafWithInScroll(const std::vector<unique_ptr<Widget>>& widgetsInScroll)
56     {
57         std::string parentHie = "ROOT";
58         int index = -1;
59         for (auto &tempWid : widgetsInScroll) {
60             const std::string &curHie = tempWid->GetHierarchy();
61             if (curHie.find(parentHie) == std::string::npos) {
62                 break;
63             }
64             parentHie = curHie;
65             ++index;
66         }
67         return index;
68     }
69 
ConstructScrollFindSelector(const WidgetSelector &selector, const string &hashcode, const string &appName, ApiCallErr &error)70     static WidgetSelector ConstructScrollFindSelector(const WidgetSelector &selector, const string &hashcode,
71         const string &appName, ApiCallErr &error)
72     {
73         WidgetSelector newSelector = selector;
74         WidgetMatchModel anchorModel{UiAttr::HASHCODE, hashcode, ValueMatchPattern::EQ};
75         WidgetSelector parentSelector{};
76         parentSelector.AddMatcher(anchorModel);
77         newSelector.AddParentLocator(parentSelector, error);
78         newSelector.AddAppLocator(appName);
79         newSelector.SetWantMulti(false);
80         return newSelector;
81     }
82 
WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options)83     WidgetOperator::WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options)
84         : driver_(driver), widget_(widget), options_(options)
85     {
86     }
87 
GenericClick(TouchOp op, ApiCallErr &error) const88     void WidgetOperator::GenericClick(TouchOp op, ApiCallErr &error) const
89     {
90         DCHECK(op >= TouchOp::CLICK && op <= TouchOp::DOUBLE_CLICK_P);
91         auto retrieved = driver_.RetrieveWidget(widget_, error, true);
92         if (error.code_ != NO_ERROR) {
93             return;
94         }
95         const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY());
96         auto touch = OHOS::uitest::GenericClick(op, center);
97         driver_.PerformTouch(touch, options_, error);
98     }
99 
ScrollToEnd(bool toTop, ApiCallErr &error) const100     void WidgetOperator::ScrollToEnd(bool toTop, ApiCallErr &error) const
101     {
102         int turnDis = -1;
103         std::unique_ptr<Widget> lastTopLeafWidget = nullptr;
104         std::unique_ptr<Widget> lastBottomLeafWidget = nullptr;
105         while (true) {
106             auto hostApp = driver_.GetHostApp(widget_);
107             WidgetSelector selector{};
108             ConstructNoFilterInWidgetSelector(selector, hostApp, widget_.GetAttr(UiAttr::HASHCODE));
109             std::vector<unique_ptr<Widget>> widgetsInScroll;
110             driver_.FindWidgets(selector, widgetsInScroll, error, true);
111             if (error.code_ != NO_ERROR) {
112                 LOG_E("There is error when ScrollToEnd, msg is %{public}s", error.message_.c_str());
113                 return;
114             }
115             if (widgetsInScroll.empty()) {
116                 LOG_I("There is no child when ScrollToEnd");
117                 return;
118             }
119             if (toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastTopLeafWidget)) {
120                 return;
121             }
122             if (!toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastBottomLeafWidget)) {
123                 return;
124             }
125             if (toTop) {
126                 int index = CalcFirstLeafWithInScroll(widgetsInScroll);
127                 if (index < 0) {
128                     LOG_E("There is error when Find Widget's fist leaf");
129                     return;
130                 }
131                 lastTopLeafWidget = std::move(widgetsInScroll.at(index));
132                 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1));
133             } else {
134                 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1));
135             }
136             TurnPage(toTop, turnDis, error);
137         }
138     }
139 
DragIntoWidget(const Widget &another, ApiCallErr &error) const140     void WidgetOperator::DragIntoWidget(const Widget &another, ApiCallErr &error) const
141     {
142         auto widgetFrom = driver_.RetrieveWidget(widget_, error);
143         if (widgetFrom == nullptr || error.code_ != NO_ERROR) {
144             return;
145         }
146         auto boundsFrom = widgetFrom->GetBounds();
147         auto widgetTo = driver_.RetrieveWidget(another, error, false);
148         if (widgetTo == nullptr || error.code_ != NO_ERROR) {
149             return;
150         }
151         auto boundsTo = widgetTo->GetBounds();
152         auto centerFrom = Point(boundsFrom.GetCenterX(), boundsFrom.GetCenterY());
153         auto centerTo = Point(boundsTo.GetCenterX(), boundsTo.GetCenterY());
154         auto touch = GenericSwipe(TouchOp::DRAG, centerFrom, centerTo);
155         driver_.PerformTouch(touch, options_, error);
156     }
157 
PinchWidget(float_t scale, ApiCallErr &error) const158     void WidgetOperator::PinchWidget(float_t scale, ApiCallErr &error) const
159     {
160         auto retrieved = driver_.RetrieveWidget(widget_, error);
161         if (retrieved == nullptr || error.code_ != NO_ERROR) {
162             return;
163         }
164         auto rectBound = widget_.GetBounds();
165         if (scale < 0) {
166             error = ApiCallErr(ERR_INVALID_INPUT, "Please input the correct scale");
167             return;
168         }
169         auto touch = GenericPinch(rectBound, scale);
170         driver_.PerformTouch(touch, options_, error);
171     }
172 
InputText(string_view text, ApiCallErr &error) const173     void WidgetOperator::InputText(string_view text, ApiCallErr &error) const
174     {
175         auto retrieved = driver_.RetrieveWidget(widget_, error);
176         if (retrieved == nullptr || error.code_ != NO_ERROR) {
177             return;
178         }
179         auto origText = retrieved->GetAttr(UiAttr::TEXT);
180         if (origText.empty() && text.empty()) {
181             return;
182         }
183         static constexpr uint32_t focusTimeMs = 500;
184         static constexpr uint32_t typeCharTimeMs = 50;
185         vector<KeyEvent> events;
186         if (!origText.empty()) {
187             for (size_t index = 0; index < origText.size(); index++) {
188                 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DPAD_RIGHT, typeCharTimeMs});
189                 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DPAD_RIGHT, 0});
190                 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DEL, typeCharTimeMs});
191                 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DEL, 0});
192             }
193         }
194         const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY());
195         auto touch = OHOS::uitest::GenericClick(TouchOp::CLICK, center);
196         driver_.PerformTouch(touch, options_, error);
197         driver_.DelayMs(focusTimeMs); // short delay to ensure focus gaining
198         auto keyActionForDelete = KeysForwarder(events);
199         driver_.TriggerKey(keyActionForDelete, options_, error);
200         driver_.DelayMs(focusTimeMs);
201         driver_.InputText(text, error);
202     }
203 
ScrollFindWidget(const WidgetSelector &selector, ApiCallErr &error) const204     unique_ptr<Widget> WidgetOperator::ScrollFindWidget(const WidgetSelector &selector, ApiCallErr &error) const
205     {
206         bool scrollingUp = true;
207         int turnDis = -1;
208         std::unique_ptr<Widget> lastTopLeafWidget = nullptr;
209         std::unique_ptr<Widget> lastBottomLeafWidget = nullptr;
210         auto hostApp = driver_.GetHostApp(widget_);
211         auto newSelector = ConstructScrollFindSelector(selector, widget_.GetAttr(UiAttr::HASHCODE), hostApp, error);
212         while (true) {
213             std::vector<unique_ptr<Widget>> targetsInScroll;
214             driver_.FindWidgets(newSelector, targetsInScroll, error, true);
215             if (!targetsInScroll.empty()) {
216                 return std::move(targetsInScroll.at(0));
217             }
218             WidgetSelector scrollSelector;
219             ConstructNoFilterInWidgetSelector(scrollSelector, hostApp, widget_.GetAttr(UiAttr::HASHCODE));
220             std::vector<unique_ptr<Widget>> widgetsInScroll;
221             driver_.FindWidgets(scrollSelector, widgetsInScroll, error, false);
222             if (error.code_ != NO_ERROR) {
223                 LOG_E("There is error when Find Widget's subwidget, msg is %{public}s", error.message_.c_str());
224                 return nullptr;
225             }
226             if (widgetsInScroll.empty()) {
227                 LOG_I("There is no child when Find Widget's subwidget");
228                 return nullptr;
229             }
230             if (scrollingUp && IsScrolledToBorder(turnDis, widgetsInScroll, lastTopLeafWidget)) {
231                 scrollingUp = false;
232             } else if (IsScrolledToBorder(turnDis, widgetsInScroll, lastBottomLeafWidget)) {
233                 LOG_W("Scroll search widget failed: %{public}s", selector.Describe().data());
234                 return nullptr;
235             }
236             if (scrollingUp) {
237                 int index = CalcFirstLeafWithInScroll(widgetsInScroll);
238                 if (index < 0) {
239                     LOG_E("There is error when Find Widget's fist leaf");
240                     return nullptr;
241                 }
242                 lastTopLeafWidget = std::move(widgetsInScroll.at(index));
243                 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1));
244             } else {
245                 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1));
246             }
247             TurnPage(scrollingUp, turnDis, error);
248         }
249     }
250 
TurnPage(bool toTop, int &oriDistance, ApiCallErr &error) const251     void WidgetOperator::TurnPage(bool toTop, int &oriDistance, ApiCallErr &error) const
252     {
253         auto bounds = widget_.GetBounds();
254         Point topPoint(bounds.GetCenterX(), bounds.top_);
255         Point bottomPoint(bounds.GetCenterX(), bounds.bottom_);
256         if (options_.scrollWidgetDeadZone_ > 0) {
257             topPoint.py_ += options_.scrollWidgetDeadZone_;
258             bottomPoint.py_ -= options_.scrollWidgetDeadZone_;
259         }
260         auto screenSize = driver_.GetDisplaySize(error);
261         auto gestureZone = screenSize.py_ / 20;
262         if (screenSize.py_ - bounds.bottom_ <= gestureZone) {
263             bottomPoint.py_ = bottomPoint.py_ - gestureZone;
264         }
265         auto touch = (toTop) ? GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint)
266                              : GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint);
267         driver_.PerformTouch(touch, options_, error);
268         oriDistance = std::abs(topPoint.py_ - bottomPoint.py_);
269         if (toTop) {
270             LOG_I("turn page from %{public}d to %{public}d", topPoint.py_, bottomPoint.py_);
271         } else {
272             LOG_I("turn page from %{public}d to %{public}d", bottomPoint.py_, topPoint.py_);
273         }
274     }
275 } // namespace OHOS::uitest
276