• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> &params)
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