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