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,bool vertical)24 static bool IsScrolledToBorder(int oriDis, 25 const std::vector<unique_ptr<Widget>> &allWidgets, 26 const unique_ptr<Widget> &anchorLeafWidget, 27 bool vertical) 28 { 29 if (oriDis < 0) { 30 return false; 31 } 32 size_t index = 0; 33 for (; index < allWidgets.size(); ++index) { 34 if (allWidgets.at(index)->GetAttr(UiAttr::ACCESSIBILITY_ID) == 35 anchorLeafWidget->GetAttr(UiAttr::ACCESSIBILITY_ID)) { 36 if (vertical) { 37 return std::abs(allWidgets.at(index)->GetBounds().top_ - anchorLeafWidget->GetBounds().top_) < 38 oriDis * SCROLL_MOVE_FACTOR; 39 } else { 40 return std::abs(allWidgets.at(index)->GetBounds().left_ - anchorLeafWidget->GetBounds().left_) < 41 oriDis * SCROLL_MOVE_FACTOR; 42 } 43 } 44 } 45 return false; 46 } 47 ConstructNoFilterInWidgetSelector(WidgetSelector & scrollSelector,const std::string & hostApp,const std::string & hashCode)48 static void ConstructNoFilterInWidgetSelector(WidgetSelector &scrollSelector, 49 const std::string &hostApp, 50 const std::string &hashCode) 51 { 52 WidgetSelector parentStrategy; 53 WidgetMatchModel anchorModel2{UiAttr::HASHCODE, hashCode, ValueMatchPattern::EQ}; 54 parentStrategy.AddMatcher(anchorModel2); 55 scrollSelector.AddAppLocator(hostApp); 56 auto error = ApiCallErr(NO_ERROR); 57 scrollSelector.AddParentLocator(parentStrategy, error); 58 scrollSelector.SetWantMulti(true); 59 } 60 CalcFirstLeafWithInScroll(const std::vector<unique_ptr<Widget>> & widgetsInScroll)61 static int CalcFirstLeafWithInScroll(const std::vector<unique_ptr<Widget>>& widgetsInScroll) 62 { 63 std::string parentHie = "ROOT"; 64 int index = -1; 65 for (auto &tempWid : widgetsInScroll) { 66 const std::string &curHie = tempWid->GetHierarchy(); 67 if (curHie.find(parentHie) == std::string::npos) { 68 break; 69 } 70 parentHie = curHie; 71 ++index; 72 } 73 return index; 74 } 75 ConstructScrollFindSelector(const WidgetSelector & selector,const string & hashcode,const string & appName,ApiCallErr & error)76 static WidgetSelector ConstructScrollFindSelector(const WidgetSelector &selector, const string &hashcode, 77 const string &appName, ApiCallErr &error) 78 { 79 WidgetSelector newSelector = selector; 80 WidgetMatchModel anchorModel{UiAttr::HASHCODE, hashcode, ValueMatchPattern::EQ}; 81 WidgetSelector parentSelector{}; 82 parentSelector.AddMatcher(anchorModel); 83 newSelector.AddParentLocator(parentSelector, error); 84 newSelector.AddAppLocator(appName); 85 newSelector.SetWantMulti(false); 86 return newSelector; 87 } 88 WidgetOperator(UiDriver & driver,const Widget & widget,const UiOpArgs & options)89 WidgetOperator::WidgetOperator(UiDriver &driver, const Widget &widget, const UiOpArgs &options) 90 : driver_(driver), widget_(widget), options_(options) 91 { 92 } 93 GenericClick(TouchOp op,ApiCallErr & error) const94 void WidgetOperator::GenericClick(TouchOp op, ApiCallErr &error) const 95 { 96 DCHECK(op >= TouchOp::CLICK && op <= TouchOp::DOUBLE_CLICK_P); 97 auto retrieved = driver_.RetrieveWidget(widget_, error, true); 98 if (retrieved == nullptr || error.code_ != NO_ERROR) { 99 return; 100 } 101 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY(), 102 retrieved->GetDisplayId()); 103 auto touch = OHOS::uitest::GenericClick(op, center); 104 driver_.PerformTouch(touch, options_, error); 105 } 106 ScrollToEnd(bool toTop,ApiCallErr & error) const107 void WidgetOperator::ScrollToEnd(bool toTop, ApiCallErr &error) const 108 { 109 auto retrieved = driver_.RetrieveWidget(widget_, error, true); 110 if (retrieved == nullptr || error.code_ != NO_ERROR) { 111 return; 112 } 113 int turnDis = -1; 114 std::unique_ptr<Widget> lastTopLeafWidget = nullptr; 115 std::unique_ptr<Widget> lastBottomLeafWidget = nullptr; 116 while (true) { 117 auto hostApp = driver_.GetHostApp(widget_); 118 WidgetSelector selector{}; 119 ConstructNoFilterInWidgetSelector(selector, hostApp, widget_.GetAttr(UiAttr::HASHCODE)); 120 std::vector<unique_ptr<Widget>> widgetsInScroll; 121 driver_.FindWidgets(selector, widgetsInScroll, error, true); 122 if (error.code_ != NO_ERROR) { 123 LOG_E("There is error when ScrollToEnd, msg is %{public}s", error.message_.c_str()); 124 return; 125 } 126 if (widgetsInScroll.empty()) { 127 LOG_I("There is no child when ScrollToEnd"); 128 return; 129 } 130 if (toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastTopLeafWidget, true)) { 131 return; 132 } 133 if (!toTop && IsScrolledToBorder(turnDis, widgetsInScroll, lastBottomLeafWidget, true)) { 134 return; 135 } 136 if (toTop) { 137 int index = CalcFirstLeafWithInScroll(widgetsInScroll); 138 if (index < 0) { 139 LOG_E("There is error when Find Widget's fist leaf"); 140 return; 141 } 142 lastTopLeafWidget = std::move(widgetsInScroll.at(index)); 143 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 144 } else { 145 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 146 } 147 TurnPage(toTop, turnDis, true, error); 148 } 149 } 150 DragIntoWidget(const Widget & another,ApiCallErr & error) const151 void WidgetOperator::DragIntoWidget(const Widget &another, ApiCallErr &error) const 152 { 153 auto widgetFrom = driver_.RetrieveWidget(widget_, error); 154 if (widgetFrom == nullptr || error.code_ != NO_ERROR) { 155 return; 156 } 157 auto boundsFrom = widgetFrom->GetBounds(); 158 auto widgetTo = driver_.RetrieveWidget(another, error, false); 159 if (widgetTo == nullptr || error.code_ != NO_ERROR) { 160 return; 161 } 162 auto boundsTo = widgetTo->GetBounds(); 163 auto centerFrom = Point(boundsFrom.GetCenterX(), boundsFrom.GetCenterY(), widgetFrom->GetDisplayId()); 164 auto centerTo = Point(boundsTo.GetCenterX(), boundsTo.GetCenterY(), widgetTo->GetDisplayId()); 165 auto touch = GenericSwipe(TouchOp::DRAG, centerFrom, centerTo); 166 driver_.PerformTouch(touch, options_, error); 167 } 168 PinchWidget(float_t scale,ApiCallErr & error) const169 void WidgetOperator::PinchWidget(float_t scale, ApiCallErr &error) const 170 { 171 auto retrieved = driver_.RetrieveWidget(widget_, error); 172 if (retrieved == nullptr || error.code_ != NO_ERROR) { 173 return; 174 } 175 auto rectBound = retrieved->GetBounds(); 176 if (scale < 0) { 177 error = ApiCallErr(ERR_INVALID_INPUT, "Please input the correct scale"); 178 return; 179 } 180 auto touch = GenericPinch(rectBound, scale); 181 driver_.PerformTouch(touch, options_, error); 182 } 183 InputText(string_view text,ApiCallErr & error) const184 void WidgetOperator::InputText(string_view text, ApiCallErr &error) const 185 { 186 auto retrieved = driver_.RetrieveWidget(widget_, error); 187 if (retrieved == nullptr || error.code_ != NO_ERROR) { 188 return; 189 } 190 auto origText = retrieved->GetAttr(UiAttr::TEXT); 191 if (origText.empty() && text.empty()) { 192 return; 193 } 194 static constexpr uint32_t focusTimeMs = 500; 195 static constexpr uint32_t typeCharTimeMs = 50; 196 vector<KeyEvent> events; 197 if (!origText.empty()) { 198 for (size_t index = 0; index < origText.size(); index++) { 199 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DPAD_RIGHT, typeCharTimeMs}); 200 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DPAD_RIGHT, 0}); 201 events.emplace_back(KeyEvent{ActionStage::DOWN, KEYCODE_DEL, typeCharTimeMs}); 202 events.emplace_back(KeyEvent{ActionStage::UP, KEYCODE_DEL, 0}); 203 } 204 } 205 const auto center = Point(retrieved->GetBounds().GetCenterX(), retrieved->GetBounds().GetCenterY(), 206 retrieved->GetDisplayId()); 207 auto touch = OHOS::uitest::GenericClick(TouchOp::CLICK, center); 208 driver_.PerformTouch(touch, options_, error); 209 driver_.DelayMs(focusTimeMs); // short delay to ensure focus gaining 210 auto keyActionForDelete = KeysForwarder(events); 211 driver_.TriggerKey(keyActionForDelete, options_, error); 212 driver_.DelayMs(focusTimeMs); 213 driver_.InputText(text, error); 214 } 215 ScrollFindWidget(const WidgetSelector & selector,bool vertical,ApiCallErr & error) const216 unique_ptr<Widget> WidgetOperator::ScrollFindWidget(const WidgetSelector &selector, 217 bool vertical, ApiCallErr &error) const 218 { 219 auto retrieved = driver_.RetrieveWidget(widget_, error); 220 if (retrieved == nullptr || error.code_ != NO_ERROR) { 221 return nullptr; 222 } 223 bool scrollingUp = true; 224 int turnDis = -1; 225 std::unique_ptr<Widget> lastTopLeafWidget = nullptr; 226 std::unique_ptr<Widget> lastBottomLeafWidget = nullptr; 227 auto hostApp = driver_.GetHostApp(widget_); 228 auto newSelector = ConstructScrollFindSelector(selector, widget_.GetAttr(UiAttr::HASHCODE), hostApp, error); 229 while (true) { 230 std::vector<unique_ptr<Widget>> targetsInScroll; 231 driver_.FindWidgets(newSelector, targetsInScroll, error, true); 232 if (!targetsInScroll.empty()) { 233 return std::move(targetsInScroll.at(0)); 234 } 235 WidgetSelector scrollSelector; 236 ConstructNoFilterInWidgetSelector(scrollSelector, hostApp, widget_.GetAttr(UiAttr::HASHCODE)); 237 std::vector<unique_ptr<Widget>> widgetsInScroll; 238 driver_.FindWidgets(scrollSelector, widgetsInScroll, error, false); 239 if (error.code_ != NO_ERROR) { 240 LOG_E("There is error when Find Widget's subwidget, msg is %{public}s", error.message_.c_str()); 241 return nullptr; 242 } 243 if (widgetsInScroll.empty()) { 244 LOG_I("There is no child when Find Widget's subwidget"); 245 return nullptr; 246 } 247 if (scrollingUp && IsScrolledToBorder(turnDis, widgetsInScroll, lastTopLeafWidget, vertical)) { 248 scrollingUp = false; 249 } else if (IsScrolledToBorder(turnDis, widgetsInScroll, lastBottomLeafWidget, vertical)) { 250 LOG_W("Scroll search widget failed: %{public}s", selector.Describe().data()); 251 return nullptr; 252 } 253 if (scrollingUp) { 254 int index = CalcFirstLeafWithInScroll(widgetsInScroll); 255 if (index < 0) { 256 LOG_E("There is error when Find Widget's fist leaf"); 257 return nullptr; 258 } 259 lastTopLeafWidget = std::move(widgetsInScroll.at(index)); 260 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 261 } else { 262 lastBottomLeafWidget = std::move(widgetsInScroll.at(widgetsInScroll.size() - 1)); 263 } 264 TurnPage(scrollingUp, turnDis, vertical, error); 265 } 266 } 267 CheckDeadZone(bool vertical,ApiCallErr & error)268 bool WidgetOperator::CheckDeadZone(bool vertical, ApiCallErr &error) 269 { 270 auto bounds = widget_.GetBounds(); 271 int maxDeadZone; 272 if (vertical) { 273 maxDeadZone = (bounds.bottom_ - bounds.top_) / TWO; 274 } else { 275 maxDeadZone = (bounds.right_ - bounds.left_) / TWO; 276 } 277 if (options_.scrollWidgetDeadZone_ >= maxDeadZone) { 278 error = ApiCallErr(ERR_INVALID_INPUT, "The offset is too large and exceeds the widget size."); 279 return false; 280 } else { 281 return true; 282 } 283 } TurnPage(bool toTop,int & oriDistance,bool vertical,ApiCallErr & error) const284 void WidgetOperator::TurnPage(bool toTop, int &oriDistance, bool vertical, ApiCallErr &error) const 285 { 286 auto bounds = widget_.GetBounds(); 287 Point topPoint; 288 Point bottomPoint; 289 auto screenSize = driver_.GetDisplaySize(error, widget_.GetDisplayId()); 290 auto gestureZone = (vertical) ? screenSize.py_ / 20 : screenSize.px_ / 20; 291 topPoint = vertical ? Point(bounds.GetCenterX(), bounds.top_) : Point(bounds.left_, bounds.GetCenterY()); 292 bottomPoint = vertical ? Point(bounds.GetCenterX(), bounds.bottom_) : 293 Point(bounds.right_, bounds.GetCenterY()); 294 if (vertical) { 295 if (options_.scrollWidgetDeadZone_ > 0) { 296 topPoint.py_ += options_.scrollWidgetDeadZone_; 297 bottomPoint.py_ -= options_.scrollWidgetDeadZone_; 298 } 299 if (abs(screenSize.py_ - bottomPoint.py_) <= gestureZone) { 300 bottomPoint.py_ = screenSize.py_ - gestureZone; 301 } 302 if (vertical && topPoint.py_ <= gestureZone) { 303 topPoint.py_ = gestureZone; 304 } 305 } else { 306 if (options_.scrollWidgetDeadZone_ > 0) { 307 topPoint.px_ += options_.scrollWidgetDeadZone_; 308 bottomPoint.px_ -= options_.scrollWidgetDeadZone_; 309 } 310 if (abs(screenSize.px_ - bottomPoint.px_) <= gestureZone) { 311 bottomPoint.px_ = screenSize.px_ - gestureZone; 312 } 313 if (topPoint.px_ <= gestureZone) { 314 topPoint.px_ = gestureZone; 315 } 316 } 317 topPoint.displayId_ = widget_.GetDisplayId(); 318 bottomPoint.displayId_ = widget_.GetDisplayId(); 319 auto touch = (toTop) ? GenericSwipe(TouchOp::SWIPE, topPoint, bottomPoint) 320 : GenericSwipe(TouchOp::SWIPE, bottomPoint, topPoint); 321 driver_.PerformTouch(touch, options_, error); 322 oriDistance = (vertical) ? std::abs(topPoint.py_ - bottomPoint.py_) : std::abs(topPoint.px_ - bottomPoint.px_); 323 if (vertical && toTop) { 324 LOG_I("turn page vertical from %{public}d to %{public}d", topPoint.py_, bottomPoint.py_); 325 } else if (vertical) { 326 LOG_I("turn page vertical from %{public}d to %{public}d", bottomPoint.py_, topPoint.py_); 327 } else if (toTop) { 328 LOG_I("turn page horizontal from %{public}d to %{public}d", topPoint.px_, bottomPoint.px_); 329 } else { 330 LOG_I("turn page horizontal from %{public}d to %{public}d", bottomPoint.px_, topPoint.px_); 331 } 332 } 333 } // namespace OHOS::uitest 334