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 <chrono> 17 #include <unistd.h> 18 #include <memory> 19 #include <iostream> 20 #include <fstream> 21 #include <getopt.h> 22 #include <dirent.h> 23 #include <sys/stat.h> 24 #include <sys/statfs.h> 25 #include <typeinfo> 26 #include <cstring> 27 #include <vector> 28 #include <functional> 29 #include <atomic> 30 #include <mutex> 31 #include <ctime> 32 #include <condition_variable> 33 #include <cmath> 34 #include <string> 35 #include <vector> 36 #include <cmath> 37 #include <fcntl.h> 38 #include <cstdio> 39 #include "ipc_transactor.h" 40 #include "system_ui_controller.h" 41 #include "input_manager.h" 42 #include "i_input_event_consumer.h" 43 #include "pointer_event.h" 44 #include "ui_driver.h" 45 #include "ui_record.h" 46 #include "ui_input.h" 47 #include "ui_model.h" 48 #include "extension_executor.h" 49 #include "hisysevent.h" 50 51 using namespace std; 52 using namespace std::chrono; 53 54 namespace OHOS::uitest { 55 const std::string HELP_MSG = 56 "usage: uitest <command> [options] \n" 57 "help print help messages\n" 58 "screenCap capture the current screen\n" 59 " -p <savePath> specifies the savePath\n" 60 " -d <displayId> specifies the screen\n" 61 "dumpLayout get the current layout information\n" 62 " -p <savePath> specifies the savePath\n" 63 " -i not merge windows and filter nodes\n" 64 " -a include font attributes\n" 65 " -b <bundleName> specifies the bundleName of the target window\n" 66 " -w <windowId> specifies the window id of the target window\n" 67 " -m <true/false> whether merge windows, true means to merge, set it true when not use this option\n" 68 " -d <displayId> specifies the locate screen of the target window\n" 69 "start-daemon <token> start the test process\n" 70 "uiRecord recording Ui Operations\n" 71 " record Write Ui event information into csv file\n" 72 " -W <true/false> whether save widget information, true means to save\n" 73 " set it true when not use this option\n" 74 " -l Save the current layout information after each operation\n" 75 " -c <true/false> whether print the Ui event information to the console, true means to print\n" 76 " set it true when not use this option\n" 77 " read print file content to the console\n" 78 "uiInput inject Ui simulation operations\n" 79 " help print uiInput usage\n" 80 " dircFling <direction> [velocity] [stepLength] direction ranges from 0,1,2,3 (left, right, up, down)\n" 81 " click/doubleClick/longClick <x> <y> click on the target coordinates\n" 82 " swipe/drag <from_x> <from_y> <to_x> <to_y> [velocity] velocity ranges from 200 to 40000, default 600\n" 83 " fling <from_x> <from_y> <to_x> <to_y> [velocity] [stepLength] velocity ranges from 200 to 40000, default 600\n" 84 " keyEvent <keyID/Back/Home/Power> inject keyEvent\n" 85 " keyEvent <keyID_0> <keyID_1> [keyID_2] keyID_2 default to None \n" 86 " inputText <x> <y> <text> inputText at the target coordinate point\n" 87 " text <text> input text at the location where is already focused\n" 88 "--version print current tool version\n"; 89 90 const std::string VERSION = "6.0.2.1"; 91 struct option g_longoptions[] = { 92 {nullptr, required_argument, nullptr, 'p'}, 93 {nullptr, required_argument, nullptr, 'd'}, 94 {nullptr, no_argument, nullptr, 'i'}, 95 {nullptr, no_argument, nullptr, 'a'}, 96 {nullptr, required_argument, nullptr, 'b'}, 97 {nullptr, required_argument, nullptr, 'w'}, 98 {nullptr, required_argument, nullptr, 'm'}, 99 {nullptr, 0, nullptr, 0}}; 100 /* *Print to the console of this shell process. */ PrintToConsole(string_view message)101 static inline void PrintToConsole(string_view message) 102 { 103 std::cout << message << std::endl; 104 } 105 IsUiTestCreatedFile(const string fileName)106 static bool IsUiTestCreatedFile(const string fileName) 107 { 108 if (fileName.find("layout_") == 0 && fileName.rfind(".json") == fileName.size() - FIVE) { 109 return true; 110 } 111 if (fileName.find("screenCap_") == 0 && fileName.rfind(".png") == fileName.size() - FOUR) { 112 return true; 113 } 114 if (fileName == "record.csv") { 115 return true; 116 } 117 return false; 118 } 119 GetFileSize(const string filePath)120 static uint64_t GetFileSize(const string filePath) 121 { 122 struct stat fileStat; 123 uint64_t fileSize = stat(filePath.c_str(), &fileStat) ? 0 : static_cast<uint64_t>(fileStat.st_size); 124 return fileSize; 125 } 126 GetUiTestCreatedFileSize(const string newFilePath,string dirName)127 static uint64_t GetUiTestCreatedFileSize(const string newFilePath, string dirName) 128 { 129 uint64_t createdFileSize = 0; 130 DIR* dir = opendir(dirName.c_str()); 131 if (!dir) { 132 LOG_E("Open dir %{public}s failed.", dirName.c_str()); 133 return createdFileSize; 134 } 135 if (newFilePath != "") { 136 createdFileSize += GetFileSize(newFilePath); 137 } 138 struct dirent* file; 139 while ((file = readdir(dir)) != nullptr) { 140 if (file->d_type == DT_REG && IsUiTestCreatedFile(file->d_name)) { 141 string filePath = dirName + "/" + file->d_name; 142 if (filePath != newFilePath) { 143 createdFileSize += GetFileSize(filePath); 144 } 145 } 146 } 147 closedir(dir); 148 return createdFileSize; 149 } 150 ReportFileWriteEvent(string newFilePath)151 static void ReportFileWriteEvent(string newFilePath) 152 { 153 string partitionName = "/data"; 154 string dirName = "/data/local/tmp"; 155 struct statfs partitionStat; 156 if (statfs(partitionName.c_str(), &partitionStat) != 0) { 157 LOG_E("Get remain partition size failed, partitionName = %{public}s", partitionName.c_str()); 158 return; 159 } 160 constexpr double units = 1024.0; 161 double remainPartitionSize = (static_cast<double>(partitionStat.f_bfree) / units) * 162 (static_cast<double>(partitionStat.f_bsize) / units); 163 uint64_t createdFileSize = GetUiTestCreatedFileSize(newFilePath, dirName); 164 vector<std::string> filePaths = { dirName }; 165 vector<uint64_t> fileSizes = { createdFileSize }; 166 HiSysEventWrite(HiviewDFX::HiSysEvent::Domain::FILEMANAGEMENT, "USER_DATA_SIZE", 167 HiviewDFX::HiSysEvent::EventType::STATISTIC, 168 "COMPONENT_NAME", "arkxtest", 169 "PARTITION_NAME", partitionName, 170 "REMAIN_PARTITION_SIZE", remainPartitionSize, 171 "FILE_OR_FOLDER_PATH", filePaths, 172 "FILE_OR_FOLDER_SIZE", fileSizes); 173 } 174 GetParam(int32_t argc,char * argv[],string_view optstring,string_view usage,map<char,string> & params)175 static int32_t GetParam(int32_t argc, char *argv[], string_view optstring, string_view usage, 176 map<char, string> ¶ms) 177 { 178 int opt; 179 while ((opt = getopt_long(argc, argv, optstring.data(), g_longoptions, nullptr)) != -1) { 180 switch (opt) { 181 case '?': 182 PrintToConsole(usage); 183 return EXIT_FAILURE; 184 case 'i': 185 params.insert(pair<char, string>(opt, "true")); 186 break; 187 case 'a': 188 params.insert(pair<char, string>(opt, "true")); 189 break; 190 case 'l': 191 params.insert(pair<char, string>(opt, "true")); 192 break; 193 case 'c': 194 case 'W': 195 case 'm': 196 if (strcmp(optarg, "true") && strcmp(optarg, "false")) { 197 PrintToConsole("Invalid params"); 198 PrintToConsole(usage); 199 return EXIT_FAILURE; 200 } 201 params.insert(pair<char, string>(opt, optarg)); 202 break; 203 default: 204 params.insert(pair<char, string>(opt, optarg)); 205 break; 206 } 207 } 208 return EXIT_SUCCESS; 209 } 210 DumpLayoutImpl(const DumpOption & option,bool initController,ApiCallErr & err)211 static void DumpLayoutImpl(const DumpOption &option, bool initController, ApiCallErr &err) 212 { 213 ofstream fout; 214 fout.open(option.savePath_, ios::out | ios::binary); 215 if (!fout) { 216 err = ApiCallErr(ERR_INVALID_INPUT, "Error path:" + string(option.savePath_) + strerror(errno)); 217 return; 218 } 219 if (initController) { 220 UiDriver::RegisterController(make_unique<SysUiController>()); 221 } 222 auto driver = UiDriver(); 223 auto data = nlohmann::json(); 224 driver.DumpUiHierarchy(data, option, err); 225 if (err.code_ != NO_ERROR) { 226 fout.close(); 227 return; 228 } 229 string dumpStr = data.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace); 230 LOG_D("dumpStr size = %{public}zu", dumpStr.size()); 231 fout << dumpStr; 232 if (fout.fail()) { 233 LOG_E("Write dumpStr to file failed."); 234 } else if (fout.bad()) { 235 LOG_E("Error in write dumpStr to file."); 236 } 237 fout.close(); 238 return; 239 } 240 DumpLayout(int32_t argc,char * argv[])241 static int32_t DumpLayout(int32_t argc, char *argv[]) 242 { 243 DumpOption option; 244 auto ts = to_string(GetCurrentMicroseconds()); 245 auto savePath = "/data/local/tmp/layout_" + ts + ".json"; 246 map<char, string> params; 247 if (GetParam(argc, argv, "p:w:b:m:d:ia", HELP_MSG, params) == EXIT_FAILURE) { 248 return EXIT_FAILURE; 249 } 250 auto iter = params.find('p'); 251 option.savePath_ = (iter != params.end()) ? iter->second : savePath; 252 auto iter2 = params.find('w'); 253 option.windowId_ = (iter2 != params.end()) ? iter2->second : ""; 254 auto iter3 = params.find('b'); 255 option.bundleName_ = (iter3 != params.end()) ? iter3->second : ""; 256 option.listWindows_ = params.find('i') != params.end(); 257 option.addExternAttr_ = params.find('a') != params.end(); 258 auto iter4 = params.find('m'); 259 option.notMergeWindow_ = (iter4 != params.end()) ? iter4->second == "false" : false; 260 auto iter5 = params.find('d'); 261 option.displayId_ = (iter5 != params.end()) ? std::atoi(iter5->second.c_str()): 0; 262 if (option.listWindows_ && option.addExternAttr_) { 263 PrintToConsole("The -a and -i options cannot be used together."); 264 return EXIT_FAILURE; 265 } 266 auto err = ApiCallErr(NO_ERROR); 267 DumpLayoutImpl(option, true, err); 268 if (err.code_ == NO_ERROR) { 269 PrintToConsole("DumpLayout saved to:" + option.savePath_); 270 ReportFileWriteEvent(option.savePath_); 271 return EXIT_SUCCESS; 272 } else if (err.code_ != ERR_INITIALIZE_FAILED) { 273 PrintToConsole("DumpLayout failed:" + err.message_); 274 return EXIT_FAILURE; 275 } 276 // Cannot connect to AAMS, broadcast request to running uitest-daemon if any 277 err = ApiCallErr(NO_ERROR); 278 auto cmd = OHOS::AAFwk::Want(); 279 cmd.SetParam("savePath", string(option.savePath_)); 280 cmd.SetParam("listWindows", option.listWindows_); 281 cmd.SetParam("addExternAttr", option.addExternAttr_); 282 cmd.SetParam("bundleName", string(option.bundleName_)); 283 cmd.SetParam("windowId", string(option.windowId_)); 284 cmd.SetParam("mergeWindow", option.notMergeWindow_); 285 cmd.SetParam("displayId", to_string(option.displayId_)); 286 ApiTransactor::SendBroadcastCommand(cmd, err); 287 if (err.code_ == NO_ERROR) { 288 PrintToConsole("DumpLayout saved to:" + option.savePath_); 289 } else { 290 PrintToConsole("DumpLayout failed:" + err.message_); 291 } 292 return err.code_ == NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE; 293 } 294 ScreenCap(int32_t argc,char * argv[])295 static int32_t ScreenCap(int32_t argc, char *argv[]) 296 { 297 auto ts = to_string(GetCurrentMicroseconds()); 298 auto savePath = "/data/local/tmp/screenCap_" + ts + ".png"; 299 auto displayId = 0; 300 map<char, string> params; 301 static constexpr string_view usage = "USAGE: uitest screenCap -p <path>"; 302 if (GetParam(argc, argv, "p:d:", usage, params) == EXIT_FAILURE) { 303 return EXIT_FAILURE; 304 } 305 auto iter = params.find('p'); 306 if (iter != params.end()) { 307 savePath = iter->second; 308 } 309 auto iter2 = params.find('d'); 310 if (iter2 != params.end()) { 311 displayId = std::atoi(iter2->second.c_str()); 312 } 313 auto controller = SysUiController(); 314 stringstream errorRecv; 315 int32_t fd = open(savePath.c_str(), O_RDWR | O_CREAT, 0666); 316 if (!controller.TakeScreenCap(fd, errorRecv, displayId)) { 317 PrintToConsole("ScreenCap failed: " + errorRecv.str()); 318 return EXIT_FAILURE; 319 } 320 PrintToConsole("ScreenCap saved to " + savePath); 321 ReportFileWriteEvent(savePath); 322 return EXIT_SUCCESS; 323 } 324 TranslateToken(string_view raw)325 static string TranslateToken(string_view raw) 326 { 327 if (raw.find_first_of('@') != string_view::npos) { 328 return string(raw); 329 } 330 return "default"; 331 } 332 GetOptionForCmd(const OHOS::AAFwk::Want & cmd)333 static DumpOption GetOptionForCmd(const OHOS::AAFwk::Want &cmd) 334 { 335 DumpOption option; 336 option.savePath_ = cmd.GetStringParam("savePath"); 337 option.listWindows_ = cmd.GetBoolParam("listWindows", false); 338 option.addExternAttr_ = cmd.GetBoolParam("addExternAttr", false); 339 option.bundleName_ = cmd.GetStringParam("bundleName"); 340 option.windowId_ = cmd.GetStringParam("windowId"); 341 option.notMergeWindow_ = cmd.GetBoolParam("mergeWindow", true); 342 option.displayId_ = atoi(cmd.GetStringParam("displayId").c_str()); 343 return option; 344 } 345 StartDaemon(string_view token,int32_t argc,char * argv[])346 static int32_t StartDaemon(string_view token, int32_t argc, char *argv[]) 347 { 348 if (token.empty()) { 349 LOG_E("Empty transaction token"); 350 return EXIT_FAILURE; 351 } 352 auto transalatedToken = TranslateToken(token); 353 if (daemon(0, 0) != 0) { 354 LOG_E("Failed to daemonize current process"); 355 return EXIT_FAILURE; 356 } 357 LOG_I("Server starting up"); 358 UiDriver::RegisterController(make_unique<SysUiController>()); 359 // accept remopte dump request during deamon running (initController=false) 360 ApiTransactor::SetBroadcastCommandHandler([] (const OHOS::AAFwk::Want &cmd, ApiCallErr &err) { 361 auto option = GetOptionForCmd(cmd); 362 DumpLayoutImpl(option, false, err); 363 }); 364 if (token == "singleness") { 365 ExecuteExtension(VERSION, argc, argv); 366 LOG_I("Server exit"); 367 ApiTransactor::UnsetBroadcastCommandHandler(); 368 _Exit(0); 369 return 0; 370 } 371 ApiTransactor apiTransactServer(true); 372 auto &apiServer = FrontendApiServer::Get(); 373 auto apiHandler = std::bind(&FrontendApiServer::Call, &apiServer, placeholders::_1, placeholders::_2); 374 auto cbHandler = std::bind(&ApiTransactor::Transact, &apiTransactServer, placeholders::_1, placeholders::_2); 375 apiServer.SetCallbackHandler(cbHandler); // used for callback from server to client 376 if (!apiTransactServer.InitAndConnectPeer(transalatedToken, apiHandler)) { 377 LOG_E("Failed to initialize server"); 378 ApiTransactor::UnsetBroadcastCommandHandler(); 379 _Exit(0); 380 return EXIT_FAILURE; 381 } 382 mutex mtx; 383 unique_lock<mutex> lock(mtx); 384 condition_variable condVar; 385 apiTransactServer.SetDeathCallback([&condVar]() { 386 condVar.notify_one(); 387 }); 388 LOG_I("UiTest-daemon running, pid=%{public}d", getpid()); 389 condVar.wait(lock); 390 LOG_I("Server exit"); 391 apiTransactServer.Finalize(); 392 ApiTransactor::UnsetBroadcastCommandHandler(); 393 _Exit(0); 394 return 0; 395 } 396 UiRecord(int32_t argc,char * argv[])397 static int32_t UiRecord(int32_t argc, char *argv[]) 398 { 399 static constexpr string_view usage = "USAGE: uitest uiRecord <read|record>"; 400 std::string opt = argv[TWO]; 401 RecordOption option; 402 if (argc >= 4) { 403 if (strcmp(argv[THREE], "point") == 0) { 404 option.saveWidget = false; 405 } 406 } 407 if (opt == "record") { 408 map<char, string> params; 409 if (GetParam(argc, argv, "W:c:l", HELP_MSG, params) == EXIT_FAILURE) { 410 return EXIT_FAILURE; 411 } 412 option.saveLayout = params.find('l') != params.end(); 413 auto iter = params.find('c'); 414 option.terminalCout = (iter != params.end()) ?iter->second == "true" : true; 415 auto w = params.find('W'); 416 option.saveWidget = (w != params.end()) ?w->second == "true" : true; 417 auto controller = make_unique<SysUiController>(); 418 ApiCallErr error = ApiCallErr(NO_ERROR); 419 if (option.saveWidget || option.saveLayout) { 420 if(!controller->ConnectToSysAbility(error)){ 421 PrintToConsole(error.message_); 422 return EXIT_FAILURE; 423 } 424 } 425 UiDriver::RegisterController(move(controller)); 426 ReportFileWriteEvent(""); 427 return UiDriverRecordStart(option); 428 } else if (opt == "read") { 429 EventData::ReadEventLine(); 430 return OHOS::ERR_OK; 431 } else { 432 PrintToConsole("Illegal argument: " + opt); 433 PrintToConsole(usage); 434 return EXIT_FAILURE; 435 } 436 } 437 UiInput(int32_t argc,char * argv[])438 static int32_t UiInput(int32_t argc, char *argv[]) 439 { 440 if ((size_t)argc < INDEX_FOUR) { 441 std::cout << "Missing parameter. \n" << std::endl; 442 PrintInputMessage(); 443 return EXIT_FAILURE; 444 } 445 if ((string)argv[THREE] == "help") { 446 PrintInputMessage(); 447 return OHOS::ERR_OK; 448 } 449 auto controller = make_unique<SysUiController>(); 450 UiDriver::RegisterController(move(controller)); 451 return UiActionInput(argc, argv); 452 } 453 main(int32_t argc,char * argv[])454 extern "C" int32_t main(int32_t argc, char *argv[]) 455 { 456 if ((size_t)argc < INDEX_TWO) { 457 PrintToConsole("Missing argument"); 458 PrintToConsole(HELP_MSG); 459 _Exit(EXIT_FAILURE); 460 } 461 string command(argv[1]); 462 if (command == "dumpLayout") { 463 _Exit(DumpLayout(argc, argv)); 464 } else if (command == "start-daemon") { 465 string_view token = argc < 3 ? "" : argv[2]; 466 _Exit(StartDaemon(token, argc - THREE, argv + THREE)); 467 } else if (command == "screenCap") { 468 _Exit(ScreenCap(argc, argv)); 469 } else if (command == "uiRecord") { 470 _Exit(UiRecord(argc, argv)); 471 } else if (command == "uiInput") { 472 _Exit(UiInput(argc, argv)); 473 } else if (command == "--version") { 474 PrintToConsole(VERSION); 475 _Exit(EXIT_SUCCESS); 476 } else if (command == "help") { 477 PrintToConsole(HELP_MSG); 478 _Exit(EXIT_SUCCESS); 479 } else { 480 PrintToConsole("Illegal argument: " + command); 481 PrintToConsole(HELP_MSG); 482 _Exit(EXIT_FAILURE); 483 } 484 } 485 } 486