1 /* 2 * Copyright (c) 2021-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 <future> 17 #include "ui_driver.h" 18 19 namespace OHOS::uitest { 20 using namespace std; 21 using namespace nlohmann; 22 23 static constexpr string_view DUMMY_ATTRNAME_SELECTION = "selectionDesc"; 24 25 std::unique_ptr<UiController> UiDriver::uiController_; 26 RegisterController(std::unique_ptr<UiController> controller)27 void UiDriver::RegisterController(std::unique_ptr<UiController> controller) 28 { 29 uiController_ = move(controller); 30 } 31 RegisterUiEventListener(std::shared_ptr<UiEventListener> listener)32 void UiDriver::RegisterUiEventListener(std::shared_ptr<UiEventListener> listener) 33 { 34 uiController_->RegisterUiEventListener(listener); 35 } 36 CheckStatus(bool isConnected,ApiCallErr & error)37 bool UiDriver::CheckStatus(bool isConnected, ApiCallErr &error) 38 { 39 DCHECK(uiController_); 40 if (isConnected && !uiController_->IsWorkable()) { 41 LOG_I("Not connect to AAMS, try to reconnect"); 42 if (!uiController_->Initialize()) { 43 error = ApiCallErr(ERR_INITIALIZE_FAILED, "Can not connect to AAMS"); 44 return false; 45 } 46 } 47 return true; 48 } 49 UpdateUi(bool updateUiTree,ApiCallErr & error,bool getWidgetNodes,string targetWin)50 void UiDriver::UpdateUi(bool updateUiTree, ApiCallErr &error, bool getWidgetNodes, string targetWin) 51 { 52 if (!updateUiTree) { 53 return; 54 } 55 if (!CheckStatus(true, error)) { 56 return; 57 } 58 windows_.clear(); 59 widgetTree_ = make_unique<WidgetTree>(""); 60 vector<pair<Window, nlohmann::json>> hierarchies; 61 uiController_->GetUiHierarchy(hierarchies, getWidgetNodes, targetWin); 62 if (hierarchies.empty()) { 63 LOG_E("%{public}s", "Get windows failed"); 64 error = ApiCallErr(ERR_INTERNAL, "Get window nodes failed"); 65 return; 66 } 67 vector<unique_ptr<WidgetTree>> trees; 68 for (auto &hierarchy : hierarchies) { 69 auto tree = make_unique<WidgetTree>(""); 70 tree->ConstructFromDom(hierarchy.second, true); 71 trees.push_back(move(tree)); 72 } 73 vector<int32_t> mergedOrdres; 74 WidgetTree::MergeTrees(trees, *widgetTree_, mergedOrdres); 75 auto virtualRoot = widgetTree_->GetRootWidget(); 76 for (size_t index = 0; index < mergedOrdres.size(); index++) { 77 auto root = widgetTree_->GetChildWidget(*virtualRoot, index); 78 DCHECK(root != nullptr); 79 DCHECK(hierarchies.size() > mergedOrdres[index]); 80 auto &window = hierarchies[mergedOrdres[index]].first; 81 window.visibleBounds_ = root->GetBounds(); 82 windows_.push_back(move(window)); 83 } 84 } 85 DumpUiHierarchy(nlohmann::json & out,bool listWindows,ApiCallErr & error)86 void UiDriver::DumpUiHierarchy(nlohmann::json &out, bool listWindows, ApiCallErr &error) 87 { 88 if (listWindows) { 89 if (!CheckStatus(true, error)) { 90 return; 91 } 92 vector<pair<Window, nlohmann::json>> datas; 93 uiController_->GetUiHierarchy(datas, true); 94 out = nlohmann::json::array(); 95 for (auto& data : datas) { 96 out.push_back(data.second); 97 } 98 } else { 99 UpdateUi(true, error, true); 100 if (error.code_ != NO_ERROR || widgetTree_ == nullptr) { 101 return; 102 } 103 widgetTree_->MarshalIntoDom(out); 104 } 105 } 106 CloneFreeWidget(const Widget & from,const WidgetSelector & selector)107 static unique_ptr<Widget> CloneFreeWidget(const Widget &from, const WidgetSelector &selector) 108 { 109 auto clone = from.Clone("NONE", from.GetHierarchy()); 110 // save the selection desc as dummy attribute 111 clone->SetAttr(DUMMY_ATTRNAME_SELECTION, selector.Describe()); 112 return clone; 113 } 114 GetHostApp(const Widget & widget)115 string UiDriver::GetHostApp(const Widget &widget) 116 { 117 auto winId = widget.GetAttr(ATTR_NAMES[UiAttr::HOST_WINDOW_ID], "0"); 118 auto id = atoi(winId.c_str()); 119 for (auto window: windows_) { 120 if (id == window.id_) { 121 // If not a actived window, get all. 122 if (window.actived_ == false) { 123 return ""; 124 } 125 return window.bundleName_; 126 } 127 } 128 return ""; 129 } 130 RetrieveWidget(const Widget & widget,ApiCallErr & err,bool updateUi)131 const Widget *UiDriver::RetrieveWidget(const Widget &widget, ApiCallErr &err, bool updateUi) 132 { 133 if (updateUi) { 134 auto hostApp = this->GetHostApp(widget); 135 UpdateUi(true, err, true, hostApp); 136 if (err.code_ != NO_ERROR) { 137 return nullptr; 138 } 139 } 140 // retrieve widget by hashcode or by hierarchy 141 constexpr auto attrHashCode = ATTR_NAMES[UiAttr::HASHCODE]; 142 constexpr auto attrHierarchy = ATTR_NAMES[UiAttr::HIERARCHY]; 143 auto hashcodeMatcher = WidgetAttrMatcher(attrHashCode, widget.GetAttr(attrHashCode, "NA"), EQ); 144 auto hierarchyMatcher = WidgetAttrMatcher(attrHierarchy, widget.GetHierarchy(), EQ); 145 auto anyMatcher = Any(hashcodeMatcher, hierarchyMatcher); 146 vector<reference_wrapper<const Widget>> recv; 147 auto visitor = MatchedWidgetCollector(anyMatcher, recv); 148 widgetTree_->DfsTraverse(visitor); 149 stringstream msg; 150 msg << "Widget: " << widget.GetAttr(DUMMY_ATTRNAME_SELECTION, ""); 151 msg << "dose not exist on current UI! Check if the UI has changed after you got the widget object"; 152 if (recv.empty()) { 153 msg << "(NoCandidates)"; 154 err = ApiCallErr(ERR_COMPONENT_LOST, msg.str()); 155 LOG_W("%{public}s", err.message_.c_str()); 156 return nullptr; 157 } 158 DCHECK(recv.size() == 1); 159 auto &retrieved = recv.at(0).get(); 160 // confirm type 161 constexpr auto attrType = ATTR_NAMES[UiAttr::TYPE]; 162 if (widget.GetAttr(attrType, "A").compare(retrieved.GetAttr(attrType, "B")) != 0) { 163 msg << " (CompareEqualsFailed)"; 164 err = ApiCallErr(ERR_COMPONENT_LOST, msg.str()); 165 LOG_W("%{public}s", err.message_.c_str()); 166 return nullptr; 167 } 168 return &retrieved; 169 } 170 TriggerKey(const KeyAction & key,const UiOpArgs & opt,ApiCallErr & error)171 void UiDriver::TriggerKey(const KeyAction &key, const UiOpArgs &opt, ApiCallErr &error) 172 { 173 if (!CheckStatus(false, error)) { 174 return; 175 } 176 vector<KeyEvent> events; 177 key.ComputeEvents(events, opt); 178 if (events.empty()) { 179 return; 180 } 181 uiController_->InjectKeyEventSequence(events); 182 uiController_->WaitForUiSteady(opt.uiSteadyThresholdMs_, opt.waitUiSteadyMaxMs_); 183 } 184 FindWidgets(const WidgetSelector & select,vector<unique_ptr<Widget>> & rev,ApiCallErr & err,bool updateUi)185 void UiDriver::FindWidgets(const WidgetSelector &select, vector<unique_ptr<Widget>> &rev, 186 ApiCallErr &err, bool updateUi) 187 { 188 if (updateUi) { 189 auto hostApp = select.GetAppLocator(); 190 UpdateUi(true, err, true, hostApp); 191 if (err.code_ != NO_ERROR) { 192 return; 193 } 194 } 195 vector<reference_wrapper<const Widget>> widgets; 196 select.Select(*widgetTree_, widgets); 197 // covert widgets to images as return value 198 uint32_t index = 0; 199 for (auto &ref : widgets) { 200 auto image = CloneFreeWidget(ref.get(), select); 201 // at sometime, more than one widgets are found, add the node index to the description 202 auto selectionDesc = select.Describe() + "(index=" + to_string(index) + ")"; 203 image->SetAttr(DUMMY_ATTRNAME_SELECTION, selectionDesc); 204 rev.emplace_back(move(image)); 205 index++; 206 } 207 } 208 WaitForWidget(const WidgetSelector & select,const UiOpArgs & opt,ApiCallErr & err)209 unique_ptr<Widget> UiDriver::WaitForWidget(const WidgetSelector &select, const UiOpArgs &opt, ApiCallErr &err) 210 { 211 const uint32_t sliceMs = 20; 212 const auto startMs = GetCurrentMillisecond(); 213 vector<unique_ptr<Widget>> receiver; 214 do { 215 FindWidgets(select, receiver, err); 216 if (err.code_ != NO_ERROR) { // abort on error 217 return nullptr; 218 } 219 if (!receiver.empty()) { 220 return move(receiver.at(0)); 221 } 222 DelayMs(sliceMs); 223 } while (GetCurrentMillisecond() - startMs < opt.waitWidgetMaxMs_); 224 return nullptr; 225 } 226 DelayMs(uint32_t ms)227 void UiDriver::DelayMs(uint32_t ms) 228 { 229 if (ms > 0) { 230 this_thread::sleep_for(chrono::milliseconds(ms)); 231 } 232 } 233 PerformTouch(const TouchAction & touch,const UiOpArgs & opt,ApiCallErr & err)234 void UiDriver::PerformTouch(const TouchAction &touch, const UiOpArgs &opt, ApiCallErr &err) 235 { 236 if (!CheckStatus(false, err)) { 237 return; 238 } 239 PointerMatrix events; 240 touch.Decompose(events, opt); 241 if (events.Empty()) { 242 return; 243 } 244 uiController_->InjectTouchEventSequence(events); 245 uiController_->WaitForUiSteady(opt.uiSteadyThresholdMs_, opt.waitUiSteadyMaxMs_); 246 } 247 TakeScreenCap(int32_t fd,ApiCallErr & err,Rect rect)248 void UiDriver::TakeScreenCap(int32_t fd, ApiCallErr &err, Rect rect) 249 { 250 if (!CheckStatus(false, err)) { 251 return; 252 } 253 stringstream errorRecv; 254 if (!uiController_->TakeScreenCap(fd, errorRecv, rect)) { 255 string errStr = errorRecv.str(); 256 LOG_W("ScreenCap failed: %{public}s", errStr.c_str()); 257 if (errStr.find("File opening failed") == 0) { 258 err = ApiCallErr(ERR_INVALID_INPUT, "Invalid save path or permission denied"); 259 } else { 260 err = ApiCallErr(ERR_INTERNAL, errStr); 261 } 262 LOG_W("ScreenCap failed: %{public}s", errorRecv.str().c_str()); 263 } else { 264 LOG_D("ScreenCap successed"); 265 } 266 } 267 FindWindow(function<bool (const Window &)> matcher,ApiCallErr & err)268 unique_ptr<Window> UiDriver::FindWindow(function<bool(const Window &)> matcher, ApiCallErr &err) 269 { 270 UpdateUi(true, err, false); 271 if (err.code_ != NO_ERROR) { 272 return nullptr; 273 } 274 for (const auto &window : windows_) { 275 if (matcher(window)) { 276 auto clone = make_unique<Window>(0); 277 *clone = window; // copy construct 278 return clone; 279 } 280 } 281 return nullptr; 282 } 283 RetrieveWindow(const Window & window,ApiCallErr & err)284 const Window *UiDriver::RetrieveWindow(const Window &window, ApiCallErr &err) 285 { 286 UpdateUi(true, err, false); 287 if (err.code_ != NO_ERROR) { 288 return nullptr; 289 } 290 for (const auto &win : windows_) { 291 if (win.id_ == window.id_) { 292 return &win; 293 } 294 } 295 stringstream msg; 296 msg << "Window " << window.id_; 297 msg << "dose not exist on current UI! Check if the UI has changed after you got the window object"; 298 err = ApiCallErr(ERR_COMPONENT_LOST, msg.str()); 299 LOG_W("%{public}s", err.message_.c_str()); 300 return nullptr; 301 } 302 SetDisplayRotation(DisplayRotation rotation,ApiCallErr & error)303 void UiDriver::SetDisplayRotation(DisplayRotation rotation, ApiCallErr &error) 304 { 305 if (!CheckStatus(false, error)) { 306 return; 307 } 308 uiController_->SetDisplayRotation(rotation); 309 } 310 GetDisplayRotation(ApiCallErr & error)311 DisplayRotation UiDriver::GetDisplayRotation(ApiCallErr &error) 312 { 313 if (!CheckStatus(false, error)) { 314 return ROTATION_0; 315 } 316 return uiController_->GetDisplayRotation(); 317 } 318 SetDisplayRotationEnabled(bool enabled,ApiCallErr & error)319 void UiDriver::SetDisplayRotationEnabled(bool enabled, ApiCallErr &error) 320 { 321 if (!CheckStatus(false, error)) { 322 return; 323 } 324 uiController_->SetDisplayRotationEnabled(enabled); 325 } 326 WaitForUiSteady(uint32_t idleThresholdMs,uint32_t timeoutSec,ApiCallErr & error)327 bool UiDriver::WaitForUiSteady(uint32_t idleThresholdMs, uint32_t timeoutSec, ApiCallErr &error) 328 { 329 if (!CheckStatus(false, error)) { 330 return false; 331 } 332 return uiController_->WaitForUiSteady(idleThresholdMs, timeoutSec); 333 } 334 WakeUpDisplay(ApiCallErr & error)335 void UiDriver::WakeUpDisplay(ApiCallErr &error) 336 { 337 if (!CheckStatus(false, error)) { 338 return; 339 } 340 if (uiController_->IsScreenOn()) { 341 return; 342 } else { 343 LOG_I("screen is off, turn it on"); 344 UiOpArgs uiOpArgs; 345 this->TriggerKey(Power(), uiOpArgs, error); 346 } 347 } 348 GetDisplaySize(ApiCallErr & error)349 Point UiDriver::GetDisplaySize(ApiCallErr &error) 350 { 351 if (!CheckStatus(false, error)) { 352 return Point(0, 0); 353 } 354 return uiController_->GetDisplaySize(); 355 } 356 GetDisplayDensity(ApiCallErr & error)357 Point UiDriver::GetDisplayDensity(ApiCallErr &error) 358 { 359 if (!CheckStatus(false, error)) { 360 return Point(0, 0); 361 } 362 return uiController_->GetDisplayDensity(); 363 } 364 GetCharKeyCode(char ch,int32_t & code,int32_t & ctrlCode,ApiCallErr & error)365 bool UiDriver::GetCharKeyCode(char ch, int32_t &code, int32_t &ctrlCode, ApiCallErr &error) 366 { 367 if (!CheckStatus(false, error)) { 368 return false; 369 } 370 return uiController_->GetCharKeyCode(ch, code, ctrlCode); 371 } 372 DfsTraverseTree(WidgetVisitor & visitor,const Widget * widget)373 void UiDriver::DfsTraverseTree(WidgetVisitor &visitor, const Widget *widget) 374 { 375 if (widgetTree_ == nullptr) { 376 return; 377 } 378 if (widget == nullptr) { 379 widgetTree_->DfsTraverse(visitor); 380 } else { 381 widgetTree_->DfsTraverseDescendants(visitor, *widget); 382 } 383 } 384 InjectMouseAction(MouseOpArgs mouseOpArgs,ApiCallErr & error)385 void UiDriver::InjectMouseAction(MouseOpArgs mouseOpArgs, ApiCallErr &error) 386 { 387 if (error.code_ != NO_ERROR) { 388 return; 389 } 390 switch (mouseOpArgs.action_) { 391 case MouseOp::M_MOVETO: 392 uiController_->InjectMouseMove(mouseOpArgs); 393 break; 394 case MouseOp::M_CLICK: 395 uiController_->InjectMouseClick(mouseOpArgs); 396 break; 397 case MouseOp::M_SCROLL: 398 uiController_->InjectMouseScroll(mouseOpArgs); 399 break; 400 default: 401 return; 402 } 403 } 404 GetLayoutJson(nlohmann::json & dom)405 void UiDriver::GetLayoutJson(nlohmann::json &dom) 406 { 407 widgetTree_->MarshalIntoDom(dom); 408 } 409 } // namespace OHOS::uitest 410