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 <typeinfo>
25 #include <cstring>
26 #include <vector>
27 #include <functional>
28 #include <atomic>
29 #include <mutex>
30 #include <ctime>
31 #include <condition_variable>
32 #include <cmath>
33 #include <string>
34 #include <vector>
35 #include <cmath>
36 #include <fcntl.h>
37 #include "ipc_transactor.h"
38 #include "system_ui_controller.h"
39 #include "input_manager.h"
40 #include "i_input_event_consumer.h"
41 #include "pointer_event.h"
42 #include "ui_driver.h"
43 #include "ui_record.h"
44 #include "ui_input.h"
45 #include "ui_model.h"
46 #include "extension_executor.h"
47 
48 using namespace std;
49 using namespace std::chrono;
50 
51 namespace OHOS::uitest {
52     const std::string HELP_MSG =
53     "usage: uitest <command> [options]                                                                          \n"
54     "help                                                                                    print help messages\n"
55     "screenCap                                                                        capture the current screen\n"
56     "  -p                                                                                               savepath\n"
57     "dumpLayout                                                               get the current layout information\n"
58     "  -p                                                                                               savepath\n"
59     "  -i                                                                     not merge windows and filter nodes\n"
60     "  -a                                                                                include font attributes\n"
61     "start-daemon <token>                                                                 start the test process\n"
62     "uiRecord                                                                                                   \n"
63     "  record                                                    wirte location coordinates of events into files\n"
64     "  read                                                                    print file content to the console\n"
65     "uiInput                                                                                                    \n"
66     "  help                                                                                  print uiInput usage\n"
67     "  dircFling [velocity stepLength]                     direction ranges from 0,1,2,3 (left, right, up, down)\n"
68     "  click/doubleClick/longClick <x> <y>                                       click on the target coordinates\n"
69     "  swipe/drag <from_x> <from_y> <to_x> <to_y> [velocity]      velocity ranges from 200 to 40000, default 600\n"
70     "  fling <from_x> <from_y> <to_x> <to_y> [velocity]           velocity ranges from 200 to 40000, default 600\n"
71     "  keyEvent <keyID/Back/Home/Power>                                                          inject keyEvent\n"
72     "  keyEvent <keyID_0> <keyID_1> [keyID_2]                                           keyID_2 default to None \n"
73     "  inputText <x> <y> <text>                                         inputText at the target coordinate point\n"
74     "--version                                                                        print current tool version\n";
75 
76     const std::string VERSION = "5.0.1.2";
77     struct option g_longoptions[] = {
78         {"save file in this path", required_argument, nullptr, 'p'},
79         {"dump all UI trees in json array format", no_argument, nullptr, 'I'}
80     };
81     /* *Print to the console of this shell process. */
PrintToConsole(string_view message)82     static inline void PrintToConsole(string_view message)
83     {
84         std::cout << message << std::endl;
85     }
86 
GetParam(int32_t argc, char *argv[], string_view optstring, string_view usage, map<char, string> &params)87     static int32_t GetParam(int32_t argc, char *argv[], string_view optstring, string_view usage,
88         map<char, string> &params)
89     {
90         int opt;
91         while ((opt = getopt_long(argc, argv, optstring.data(), g_longoptions, nullptr)) != -1) {
92             switch (opt) {
93                 case '?':
94                     PrintToConsole(usage);
95                     return EXIT_FAILURE;
96                 case 'i':
97                     params.insert(pair<char, string>(opt, "true"));
98                     break;
99                 case 'a':
100                     params.insert(pair<char, string>(opt, "true"));
101                     break;
102                 default:
103                     params.insert(pair<char, string>(opt, optarg));
104                     break;
105             }
106         }
107         return EXIT_SUCCESS;
108     }
109 
DumpLayoutImpl(string_view path, bool listWindows, bool initController, bool addExternAttr, ApiCallErr &err)110     static void DumpLayoutImpl(string_view path, bool listWindows, bool initController, bool addExternAttr,
111         ApiCallErr &err)
112     {
113         ofstream fout;
114         fout.open(path, ios::out | ios::binary);
115         if (!fout) {
116             err = ApiCallErr(ERR_INVALID_INPUT, "Error path:" + string(path) + strerror(errno));
117             return;
118         }
119         if (initController) {
120             UiDriver::RegisterController(make_unique<SysUiController>());
121         }
122         auto driver = UiDriver();
123         auto data = nlohmann::json();
124         driver.DumpUiHierarchy(data, listWindows, addExternAttr, err);
125         if (err.code_ != NO_ERROR) {
126             fout.close();
127             return;
128         }
129         fout << data.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace);
130         fout.close();
131         return;
132     }
133 
DumpLayout(int32_t argc, char *argv[])134     static int32_t DumpLayout(int32_t argc, char *argv[])
135     {
136         auto ts = to_string(GetCurrentMicroseconds());
137         auto savePath = "/data/local/tmp/layout_" + ts + ".json";
138         map<char, string> params;
139         static constexpr string_view usage = "USAGE: uitestkit dumpLayout -p <path>";
140         if (GetParam(argc, argv, "p:ia", usage, params) == EXIT_FAILURE) {
141             return EXIT_FAILURE;
142         }
143         auto iter = params.find('p');
144         if (iter != params.end()) {
145             savePath = iter->second;
146         }
147         const bool listWindows = params.find('i') != params.end();
148         const bool addExternAttr = params.find('a') != params.end();
149         auto err = ApiCallErr(NO_ERROR);
150         DumpLayoutImpl(savePath, listWindows, true, addExternAttr, err);
151         if (err.code_ == NO_ERROR) {
152             PrintToConsole("DumpLayout saved to:" + savePath);
153             return EXIT_SUCCESS;
154         } else if (err.code_ != ERR_INITIALIZE_FAILED) {
155             PrintToConsole("DumpLayout failed:" + err.message_);
156             return EXIT_FAILURE;
157         }
158         // Cannot connect to AAMS, broadcast request to running uitest-daemon if any
159         err = ApiCallErr(NO_ERROR);
160         auto cmd = OHOS::AAFwk::Want();
161         cmd.SetParam("savePath", string(savePath));
162         cmd.SetParam("listWindows", listWindows);
163         ApiTransactor::SendBroadcastCommand(cmd, err);
164         if (err.code_ == NO_ERROR) {
165             PrintToConsole("DumpLayout saved to:" + savePath);
166             return EXIT_SUCCESS;
167         } else {
168             PrintToConsole("DumpLayout failed:" + err.message_);
169             return EXIT_FAILURE;
170         }
171     }
172 
ScreenCap(int32_t argc, char *argv[])173     static int32_t ScreenCap(int32_t argc, char *argv[])
174     {
175         auto ts = to_string(GetCurrentMicroseconds());
176         auto savePath = "/data/local/tmp/screenCap_" + ts + ".png";
177         map<char, string> params;
178         static constexpr string_view usage = "USAGE: uitest screenCap -p <path>";
179         if (GetParam(argc, argv, "p:", usage, params) == EXIT_FAILURE) {
180             return EXIT_FAILURE;
181         }
182         auto iter = params.find('p');
183         if (iter != params.end()) {
184             savePath = iter->second;
185         }
186         auto controller = SysUiController();
187         stringstream errorRecv;
188         auto fd = open(savePath.c_str(), O_RDWR | O_CREAT, 0666);
189         if (!controller.TakeScreenCap(fd, errorRecv)) {
190             PrintToConsole("ScreenCap failed: " + errorRecv.str());
191             return EXIT_FAILURE;
192         }
193         PrintToConsole("ScreenCap saved to " + savePath);
194         (void) close(fd);
195         return EXIT_SUCCESS;
196     }
197 
TranslateToken(string_view raw)198     static string TranslateToken(string_view raw)
199     {
200         if (raw.find_first_of('@') != string_view::npos) {
201             return string(raw);
202         }
203         return "default";
204     }
205 
StartDaemon(string_view token, int32_t argc, char *argv[])206     static int32_t StartDaemon(string_view token, int32_t argc, char *argv[])
207     {
208         if (token.empty()) {
209             LOG_E("Empty transaction token");
210             return EXIT_FAILURE;
211         }
212         auto transalatedToken = TranslateToken(token);
213         if (daemon(0, 0) != 0) {
214             LOG_E("Failed to daemonize current process");
215             return EXIT_FAILURE;
216         }
217         LOG_I("Server starting up");
218         UiDriver::RegisterController(make_unique<SysUiController>());
219         // accept remopte dump request during deamon running (initController=false)
220         ApiTransactor::SetBroadcastCommandHandler([] (const OHOS::AAFwk::Want &cmd, ApiCallErr &err) {
221             DumpLayoutImpl(cmd.GetStringParam("savePath"), cmd.GetBoolParam("listWindows", false), false, false, err);
222         });
223         std::string_view singlenessToken = "singleness";
224         if (token == singlenessToken) {
225             ExecuteExtension(VERSION, argc, argv);
226             LOG_I("Server exit");
227             ApiTransactor::UnsetBroadcastCommandHandler();
228             _Exit(0);
229             return 0;
230         }
231         ApiTransactor apiTransactServer(true);
232         auto &apiServer = FrontendApiServer::Get();
233         auto apiHandler = std::bind(&FrontendApiServer::Call, &apiServer, placeholders::_1, placeholders::_2);
234         auto cbHandler = std::bind(&ApiTransactor::Transact, &apiTransactServer, placeholders::_1, placeholders::_2);
235         apiServer.SetCallbackHandler(cbHandler); // used for callback from server to client
236         if (!apiTransactServer.InitAndConnectPeer(transalatedToken, apiHandler)) {
237             LOG_E("Failed to initialize server");
238             ApiTransactor::UnsetBroadcastCommandHandler();
239             _Exit(0);
240             return EXIT_FAILURE;
241         }
242         mutex mtx;
243         unique_lock<mutex> lock(mtx);
244         condition_variable condVar;
245         apiTransactServer.SetDeathCallback([&condVar]() {
246             condVar.notify_one();
247         });
248         LOG_I("UiTest-daemon running, pid=%{public}d", getpid());
249         condVar.wait(lock);
250         LOG_I("Server exit");
251         apiTransactServer.Finalize();
252         ApiTransactor::UnsetBroadcastCommandHandler();
253         _Exit(0);
254         return 0;
255     }
256 
UiRecord(int32_t argc, char *argv[])257     static int32_t UiRecord(int32_t argc, char *argv[])
258     {
259         static constexpr string_view usage = "USAGE: uitest uiRecord <read|record>";
260         if (!(argc == INDEX_THREE || argc == INDEX_FOUR)) {
261             PrintToConsole("Missing parameter. \n");
262             PrintToConsole(usage);
263             return EXIT_FAILURE;
264         }
265         std::string opt = argv[TWO];
266         std::string modeOpt;
267         if (argc == INDEX_FOUR) {
268             modeOpt = argv[THREE];
269         }
270         if (opt == "record") {
271             auto controller = make_unique<SysUiController>();
272             ApiCallErr error = ApiCallErr(NO_ERROR);
273             if (!controller->ConnectToSysAbility(error)) {
274                 PrintToConsole(error.message_);
275                 return EXIT_FAILURE;
276             }
277             UiDriver::RegisterController(move(controller));
278             return UiDriverRecordStart(modeOpt);
279         } else if (opt == "read") {
280             EventData::ReadEventLine();
281             return OHOS::ERR_OK;
282         } else {
283             PrintToConsole("Illegal argument: " + opt);
284             PrintToConsole(usage);
285             return EXIT_FAILURE;
286         }
287     }
288 
UiInput(int32_t argc, char *argv[])289     static int32_t UiInput(int32_t argc, char *argv[])
290     {
291         if ((size_t)argc < INDEX_FOUR) {
292             std::cout << "Missing parameter. \n" << std::endl;
293             PrintInputMessage();
294             return EXIT_FAILURE;
295         }
296         if ((string)argv[THREE] == "help") {
297             PrintInputMessage();
298             return OHOS::ERR_OK;
299         }
300         auto controller = make_unique<SysUiController>();
301         UiDriver::RegisterController(move(controller));
302         return UiActionInput(argc, argv);
303     }
304 
main(int32_t argc, char *argv[])305     extern "C" int32_t main(int32_t argc, char *argv[])
306     {
307         if ((size_t)argc < INDEX_TWO) {
308             PrintToConsole("Missing argument");
309             PrintToConsole(HELP_MSG);
310             _Exit(EXIT_FAILURE);
311         }
312         string command(argv[1]);
313         if (command == "dumpLayout") {
314             _Exit(DumpLayout(argc, argv));
315         } else if (command == "start-daemon") {
316             string_view token = argc < 3 ? "" : argv[2];
317             _Exit(StartDaemon(token, argc - THREE, argv + THREE));
318         } else if (command == "screenCap") {
319             _Exit(ScreenCap(argc, argv));
320         } else if (command == "uiRecord") {
321             _Exit(UiRecord(argc, argv));
322         } else if (command == "uiInput") {
323             _Exit(UiInput(argc, argv));
324         } else if (command == "--version") {
325             PrintToConsole(VERSION);
326             _Exit(EXIT_SUCCESS);
327         } else if (command == "help") {
328             PrintToConsole(HELP_MSG);
329             _Exit(EXIT_SUCCESS);
330         } else {
331             PrintToConsole("Illegal argument: " + command);
332             PrintToConsole(HELP_MSG);
333             _Exit(EXIT_FAILURE);
334         }
335     }
336 }
337