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 "js_client_loader.h" 45 46 using namespace std; 47 using namespace std::chrono; 48 49 namespace OHOS::uitest { 50 const std::string HELP_MSG = 51 " help, print help messages\n" 52 " screenCap, \n" 53 " dumpLayout, \n" 54 " uiRecord record, wirte location coordinates of events into files\n" 55 " uiRecord read, print file content to the console\n" 56 " --version, print current tool version\n"; 57 const std::string VERSION = "4.0.4.2"; 58 struct option g_longoptions[] = { 59 {"save file in this path", required_argument, nullptr, 'p'}, 60 {"dump all UI trees in json array format", no_argument, nullptr, 'I'} 61 }; 62 /* *Print to the console of this shell process. */ PrintToConsole(string_view message)63 static inline void PrintToConsole(string_view message) 64 { 65 std::cout << message << std::endl; 66 } 67 GetParam(int32_t argc,char * argv[],string_view optstring,string_view usage,map<char,string> & params)68 static int32_t GetParam(int32_t argc, char *argv[], string_view optstring, string_view usage, 69 map<char, string> ¶ms) 70 { 71 int opt; 72 while ((opt = getopt_long(argc, argv, optstring.data(), g_longoptions, nullptr)) != -1) { 73 switch (opt) { 74 case '?': 75 PrintToConsole(usage); 76 return EXIT_FAILURE; 77 case 'i': 78 params.insert(pair<char, string>(opt, "true")); 79 break; 80 default: 81 params.insert(pair<char, string>(opt, optarg)); 82 break; 83 } 84 } 85 return EXIT_SUCCESS; 86 } 87 DumpLayoutImpl(string_view path,bool listWindows,bool initController,ApiCallErr & err)88 static void DumpLayoutImpl(string_view path, bool listWindows, bool initController, ApiCallErr &err) 89 { 90 ofstream fout; 91 fout.open(path, ios::out | ios::binary); 92 if (!fout) { 93 err = ApiCallErr(ERR_INVALID_INPUT, "Error path:" + string(path) + strerror(errno)); 94 return; 95 } 96 if (initController) { 97 UiDriver::RegisterController(make_unique<SysUiController>()); 98 } 99 auto driver = UiDriver(); 100 auto data = nlohmann::json(); 101 driver.DumpUiHierarchy(data, listWindows, err); 102 if (err.code_ != NO_ERROR) { 103 fout.close(); 104 return; 105 } 106 fout << data.dump(); 107 fout.close(); 108 return; 109 } 110 DumpLayout(int32_t argc,char * argv[])111 static int32_t DumpLayout(int32_t argc, char *argv[]) 112 { 113 auto ts = to_string(GetCurrentMicroseconds()); 114 auto savePath = "/data/local/tmp/layout_" + ts + ".json"; 115 map<char, string> params; 116 static constexpr string_view usage = "USAGE: uitestkit dumpLayout -p <path>"; 117 if (GetParam(argc, argv, "p:i", usage, params) == EXIT_FAILURE) { 118 return EXIT_FAILURE; 119 } 120 auto iter = params.find('p'); 121 if (iter != params.end()) { 122 savePath = iter->second; 123 } 124 const bool listWindows = params.find('i') != params.end(); 125 auto err = ApiCallErr(NO_ERROR); 126 DumpLayoutImpl(savePath, listWindows, true, err); 127 if (err.code_ == NO_ERROR) { 128 PrintToConsole("DumpLayout saved to:" + savePath); 129 return EXIT_SUCCESS; 130 } else if (err.code_ != ERR_INITIALIZE_FAILED) { 131 PrintToConsole("DumpLayout failed:" + err.message_); 132 return EXIT_FAILURE; 133 } 134 // Cannot connect to AAMS, broadcast request to running uitest-daemon if any 135 err = ApiCallErr(NO_ERROR); 136 auto cmd = OHOS::AAFwk::Want(); 137 cmd.SetParam("savePath", string(savePath)); 138 cmd.SetParam("listWindows", listWindows); 139 ApiTransactor::SendBroadcastCommand(cmd, err); 140 if (err.code_ == NO_ERROR) { 141 PrintToConsole("DumpLayout saved to:" + savePath); 142 return EXIT_SUCCESS; 143 } else { 144 PrintToConsole("DumpLayout failed:" + err.message_); 145 return EXIT_FAILURE; 146 } 147 } 148 ScreenCap(int32_t argc,char * argv[])149 static int32_t ScreenCap(int32_t argc, char *argv[]) 150 { 151 auto ts = to_string(GetCurrentMicroseconds()); 152 auto savePath = "/data/local/tmp/screenCap_" + ts + ".png"; 153 map<char, string> params; 154 static constexpr string_view usage = "USAGE: uitest screenCap -p <path>"; 155 if (GetParam(argc, argv, "p:", usage, params) == EXIT_FAILURE) { 156 return EXIT_FAILURE; 157 } 158 auto iter = params.find('p'); 159 if (iter != params.end()) { 160 savePath = iter->second; 161 } 162 auto controller = SysUiController(); 163 stringstream errorRecv; 164 auto fd = open(savePath.c_str(), O_RDWR | O_CREAT, 0666); 165 if (!controller.TakeScreenCap(fd, errorRecv)) { 166 PrintToConsole("ScreenCap failed: " + errorRecv.str()); 167 return EXIT_FAILURE; 168 } 169 PrintToConsole("ScreenCap saved to " + savePath); 170 (void) close(fd); 171 return EXIT_SUCCESS; 172 } 173 TranslateToken(string_view raw)174 static string TranslateToken(string_view raw) 175 { 176 if (raw.find_first_of('@') != string_view::npos) { 177 return string(raw); 178 } 179 return "default"; 180 } 181 StartDaemon(string_view token)182 static int32_t StartDaemon(string_view token) 183 { 184 if (token.empty()) { 185 LOG_E("Empty transaction token"); 186 return EXIT_FAILURE; 187 } 188 auto transalatedToken = TranslateToken(token); 189 if (daemon(0, 0) != 0) { 190 LOG_E("Failed to daemonize current process"); 191 return EXIT_FAILURE; 192 } 193 LOG_I("Server starting up"); 194 UiDriver::RegisterController(make_unique<SysUiController>()); 195 ApiTransactor apiTransactServer(true); 196 auto &apiServer = FrontendApiServer::Get(); 197 auto apiHandler = std::bind(&FrontendApiServer::Call, &apiServer, placeholders::_1, placeholders::_2); 198 auto cbHandler = std::bind(&ApiTransactor::Transact, &apiTransactServer, placeholders::_1, placeholders::_2); 199 apiServer.SetCallbackHandler(cbHandler); // used for callback from server to client 200 201 const auto singlenessMode = token == "singleness"; 202 future<void> g_agentFuture; 203 if (singlenessMode) { 204 g_agentFuture = async(launch::async, []() { 205 pthread_setname_np(pthread_self(), "event_runner"); 206 RunJsClient(VERSION); 207 }); 208 } 209 if (!apiTransactServer.InitAndConnectPeer(transalatedToken, apiHandler)) { 210 LOG_E("Failed to initialize server"); 211 return EXIT_FAILURE; 212 } 213 // accept remopte dump request during deamon running (initController=false) 214 ApiTransactor::SetBroadcastCommandHandler([] (const OHOS::AAFwk::Want &cmd, ApiCallErr &err) { 215 DumpLayoutImpl(cmd.GetStringParam("savePath"), cmd.GetBoolParam("listWindows", false), false, err); 216 }); 217 mutex mtx; 218 unique_lock<mutex> lock(mtx); 219 condition_variable condVar; 220 apiTransactServer.SetDeathCallback([&condVar]() { 221 condVar.notify_one(); 222 }); 223 LOG_I("UiTest-daemon running, pid=%{public}d", getpid()); 224 condVar.wait(lock); 225 LOG_I("Server exit"); 226 apiTransactServer.Finalize(); 227 ApiTransactor::UnsetBroadcastCommandHandler(); 228 _Exit(0); 229 return 0; 230 } 231 UiRecord(int32_t argc,char * argv[])232 static int32_t UiRecord(int32_t argc, char *argv[]) 233 { 234 static constexpr string_view usage = "USAGE: uitest uiRecord <read|record>"; 235 if (!(argc == INDEX_THREE || argc == INDEX_FOUR)) { 236 PrintToConsole(usage); 237 return EXIT_FAILURE; 238 } 239 std::string opt = argv[TWO]; 240 std::string modeOpt; 241 if (argc == INDEX_FOUR) { 242 modeOpt = argv[THREE]; 243 } 244 if (opt == "record") { 245 auto controller = make_unique<SysUiController>(); 246 if (!controller->ConnectToSysAbility()) { 247 PrintToConsole("Failed, cannot connect to AMMS "); 248 return EXIT_FAILURE; 249 } 250 UiDriver::RegisterController(move(controller)); 251 return UiDriverRecordStart(modeOpt); 252 } else if (opt == "read") { 253 EventData::ReadEventLine(); 254 return OHOS::ERR_OK; 255 } else { 256 PrintToConsole(usage); 257 return EXIT_FAILURE; 258 } 259 } 260 main(int32_t argc,char * argv[])261 extern "C" int32_t main(int32_t argc, char *argv[]) 262 { 263 static constexpr string_view usage = "USAGE: uitest <help|screenCap|dumpLayout|uiRecord|--version>"; 264 if ((size_t)argc < INDEX_TWO) { 265 PrintToConsole("Missing argument"); 266 PrintToConsole(usage); 267 exit(EXIT_FAILURE); 268 } 269 string command(argv[1]); 270 if (command == "dumpLayout") { 271 exit(DumpLayout(argc, argv)); 272 } else if (command == "start-daemon") { 273 string_view token = argc < 3 ? "" : argv[2]; 274 exit(StartDaemon(token)); 275 } else if (command == "screenCap") { 276 exit(ScreenCap(argc, argv)); 277 } else if (command == "uiRecord") { 278 exit(UiRecord(argc, argv)); 279 } else if (command == "--version") { 280 PrintToConsole(VERSION); 281 exit(EXIT_SUCCESS); 282 } else if (command == "help") { 283 PrintToConsole(HELP_MSG); 284 exit(EXIT_SUCCESS); 285 } else { 286 PrintToConsole("Illegal argument: " + command); 287 PrintToConsole(usage); 288 exit(EXIT_FAILURE); 289 } 290 } 291 } 292