1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | 'use strict';
|
15 |
|
16 | const android = require('./android'),
|
17 | appc = require('node-appc'),
|
18 | __ = appc.i18n(__dirname).__,
|
19 | ADB = require('./adb'),
|
20 | async = require('async'),
|
21 | events = require('events'),
|
22 | fs = require('fs'),
|
23 | path = require('path'),
|
24 | util = require('util');
|
25 | require('colors');
|
26 |
|
27 | module.exports = EmulatorManager;
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | function Emulator() {}
|
38 | util.inherits(EmulatorManager.Emulator = Emulator, events.EventEmitter);
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | function EmulatorManager(config) {
|
49 | this.config = config;
|
50 | }
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | EmulatorManager.prototype.detect = function detect(opts, callback) {
|
59 | if (opts && typeof opts === 'function') {
|
60 | callback = opts;
|
61 | opts = {};
|
62 | }
|
63 |
|
64 | var files = opts && opts.type ? [ opts.type + '.js' ] : fs.readdirSync(path.join(__dirname, 'emulators')),
|
65 | re = /\.js$/,
|
66 | config = this.config;
|
67 |
|
68 | async.parallel(files.map(function (filename) {
|
69 | return function (next) {
|
70 | var file = path.join(__dirname, 'emulators', filename);
|
71 | if (re.test(filename) && fs.existsSync(file)) {
|
72 | var module = require(file);
|
73 | if (typeof module.detect === 'function') {
|
74 | module.detect(config, opts, next);
|
75 | return;
|
76 | }
|
77 | }
|
78 | next();
|
79 | };
|
80 | }), function (err, results) {
|
81 | if (err) {
|
82 | return callback(err);
|
83 | }
|
84 |
|
85 | android.detect(this.config, opts, function (androidEnv) {
|
86 | var ver2api = {},
|
87 | emus = [];
|
88 |
|
89 | Object.keys(androidEnv.targets).forEach(function (id) {
|
90 | if (androidEnv.targets[id].type === 'platform') {
|
91 | ver2api[androidEnv.targets[id].version] = androidEnv.targets[id].sdk;
|
92 | }
|
93 | });
|
94 |
|
95 | results.forEach(function (r) {
|
96 | if (r && Array.isArray(r.avds)) {
|
97 | r.avds.forEach(function (avd) {
|
98 | if (!avd['api-level']) {
|
99 | avd['api-level'] = ver2api[avd['sdk-version']] || null;
|
100 | }
|
101 | if (!avd.id) {
|
102 | avd.id = avd.name;
|
103 | }
|
104 | emus.push(avd);
|
105 | });
|
106 | }
|
107 | });
|
108 |
|
109 | opts.logger && opts.logger.trace(__('Found %s emulators', String(emus.length).cyan));
|
110 | callback(null, emus);
|
111 | });
|
112 | }.bind(this));
|
113 | };
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | EmulatorManager.prototype.isRunning = function isRunning(id, opts, callback) {
|
123 | if (opts && typeof opts === 'function') {
|
124 | callback = opts;
|
125 | opts = {};
|
126 | }
|
127 |
|
128 | opts.logger && opts.logger.trace(__('Detecting if %s exists...', id.cyan));
|
129 |
|
130 | this.detect(opts, function (err, emus) {
|
131 | if (err) {
|
132 | return callback(err);
|
133 | }
|
134 |
|
135 | const emu = emus.filter(e => e && e.id == id).shift();
|
136 |
|
137 | if (!emu) {
|
138 | return callback(new Error(__('Invalid emulator "%s"', id)), null);
|
139 | }
|
140 |
|
141 | opts.logger && opts.logger.trace(__('Emulator exists, detecting all running emulators and connected devices...'));
|
142 |
|
143 |
|
144 | const adb = new ADB(this.config);
|
145 | adb.devices(function (err, devices) {
|
146 | if (err) {
|
147 | return callback(err);
|
148 | }
|
149 |
|
150 | opts.logger && opts.logger.trace(__('Detected %s running emulators and connected devices', String(devices.length).cyan));
|
151 |
|
152 |
|
153 | if (!devices.length) {
|
154 | return callback(null, null);
|
155 | }
|
156 |
|
157 | opts.logger && opts.logger.trace(__('Checking %s devices to see if it\'s the emulator we want', String(devices.length).cyan));
|
158 |
|
159 | require(path.join(__dirname, 'emulators', emu.type + '.js')).isRunning(this.config, emu, devices, function (err, device) {
|
160 | if (err) {
|
161 | opts.logger && opts.logger.trace(__('Failed to check if the emulator was running: %s', err));
|
162 | } else if (device) {
|
163 | opts.logger && opts.logger.trace(__('The emulator is running'));
|
164 | } else {
|
165 | opts.logger && opts.logger.trace(__('The emulator is NOT running'));
|
166 | }
|
167 | callback(err, device);
|
168 | });
|
169 | }.bind(this));
|
170 | }.bind(this));
|
171 | };
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | EmulatorManager.prototype.isEmulator = function isEmulator(device, opts, callback) {
|
181 | if (opts && typeof opts === 'function') {
|
182 | callback = opts;
|
183 | opts = {};
|
184 | }
|
185 |
|
186 | var files = opts && opts.type ? [ opts.type + '.js' ] : fs.readdirSync(path.join(__dirname, 'emulators')),
|
187 | re = /\.js$/,
|
188 | config = this.config;
|
189 |
|
190 | async.parallel(files.map(function (filename) {
|
191 | return function (next) {
|
192 | var file = path.join(__dirname, 'emulators', filename);
|
193 | if (re.test(filename) && fs.existsSync(file)) {
|
194 | var module = require(file);
|
195 | if (typeof module.isEmulator === 'function') {
|
196 | module.isEmulator(config, device, next);
|
197 | return;
|
198 | }
|
199 | }
|
200 | next();
|
201 | };
|
202 | }), function (err, results) {
|
203 | if (err) {
|
204 | callback(new Error(__('Unable to find device "%s"', device)));
|
205 | } else {
|
206 | callback(null, results.filter(n => n).shift());
|
207 | }
|
208 | });
|
209 | };
|
210 |
|
211 | function checkedBooted(config, opts, emulator) {
|
212 |
|
213 | var adb = new ADB(config),
|
214 | retryTimeout = 2000,
|
215 | bootTimeout = opts.bootTimeout || 240000,
|
216 |
|
217 |
|
218 | bootTimer = setTimeout(function () {
|
219 | opts.logger && opts.logger.trace(__('Timed out while waiting for the emulator to boot; waited %s ms', bootTimeout));
|
220 | conn && conn.end();
|
221 | bootTimeout && emulator.emit('timeout', { type: 'emulator', waited: bootTimeout });
|
222 | }, bootTimeout),
|
223 | sdcardTimeout = opts.sdcardTimeout || 60000,
|
224 | sdcardTimer,
|
225 | conn,
|
226 | deviceId,
|
227 | emu = emulator.emulator,
|
228 | emulib = require(path.join(__dirname, 'emulators', emu.type + '.js'));
|
229 |
|
230 | opts.logger && opts.logger.trace(__('Checking the boot state for the next %s ms', bootTimeout));
|
231 | opts.logger && opts.logger.trace(__('Waiting for emulator to register with ADB'));
|
232 |
|
233 | conn = adb.trackDevices(function (err, devices) {
|
234 | if (err) {
|
235 | opts.logger && opts.logger.trace(__('Error tracking devices: %s', err.message));
|
236 | return;
|
237 | } else if (!devices.length) {
|
238 | opts.logger && opts.logger.trace(__('No devices found, continuing to wait'));
|
239 | return;
|
240 | }
|
241 |
|
242 |
|
243 | if (deviceId) {
|
244 | return;
|
245 | }
|
246 |
|
247 | opts.logger && opts.logger.trace(__('Found %s devices, checking if any of them are the emulator...', devices.length));
|
248 |
|
249 | emulib.isRunning(config, emu, devices, function (err, running) {
|
250 | if (err) {
|
251 |
|
252 | opts.logger && opts.logger.trace(__('Error checking if emulator is running: %s', err));
|
253 | } else if (!running) {
|
254 |
|
255 | opts.logger && opts.logger.trace(__('Emulator not running yet, continuing to wait'));
|
256 | } else {
|
257 |
|
258 | opts.logger && opts.logger.trace(__('Emulator is running!'));
|
259 | appc.util.mix(emulator, running);
|
260 | deviceId = running.id;
|
261 | conn.end();
|
262 |
|
263 |
|
264 | opts.logger && opts.logger.trace(__('Checking if boot animation has finished...'));
|
265 | (function checkBootAnim() {
|
266 |
|
267 | adb.shell(deviceId, 'getprop init.svc.bootanim', function (err, output) {
|
268 | if (!err && output.toString().split('\n').shift().trim() === 'stopped') {
|
269 | clearTimeout(bootTimer);
|
270 | opts.logger && opts.logger.trace(__('Emulator is booted, emitting booted event'));
|
271 | emulator.emit('booted', emulator);
|
272 | } else {
|
273 | opts.logger && opts.logger.trace(__('Emulator is not booted yet; checking again in %s ms', retryTimeout));
|
274 | setTimeout(checkBootAnim, retryTimeout);
|
275 | }
|
276 | });
|
277 | }());
|
278 | }
|
279 | });
|
280 | });
|
281 |
|
282 | emulator.on('booted', function () {
|
283 | var done = false;
|
284 |
|
285 | opts.logger && opts.logger.info(__('Emulator is booted'));
|
286 |
|
287 | if (!opts.checkMounts || !emu.sdcard) {
|
288 |
|
289 | opts.logger && opts.logger.info(__('SD card not required, skipping mount check'));
|
290 | emulator.emit('ready', emulator);
|
291 | return;
|
292 | }
|
293 |
|
294 | opts.logger && opts.logger.info(__('Checking if SD card is mounted'));
|
295 |
|
296 |
|
297 | async.whilst(
|
298 | function () { return !done; },
|
299 |
|
300 | function (cb) {
|
301 |
|
302 | adb.shell(deviceId, 'cd /sdcard && echo "SDCARD READY"', function (err, output) {
|
303 | if (!err && output.toString().split('\n').shift().trim() === 'SDCARD READY') {
|
304 | done = true;
|
305 | cb();
|
306 | } else {
|
307 | setTimeout(cb, retryTimeout);
|
308 | }
|
309 | });
|
310 | },
|
311 |
|
312 | function () {
|
313 | var mounted = false,
|
314 | mountPoints = [ '/sdcard', '/mnt/sdcard' ];
|
315 |
|
316 | adb.shell(deviceId, 'ls -l /sdcard', function (err, output) {
|
317 | if (!err) {
|
318 | var m = output.toString().trim().split('\n').shift().trim().match(/-> (\S+)/);
|
319 | if (m && mountPoints.indexOf(m[1]) === -1) {
|
320 | mountPoints.unshift(m[1]);
|
321 | }
|
322 | }
|
323 |
|
324 | opts.logger && opts.logger.debug(__('Checking mount points: %s', mountPoints.join(', ').cyan));
|
325 |
|
326 |
|
327 | async.whilst(
|
328 | function () { return !mounted; },
|
329 |
|
330 | function (cb) {
|
331 | adb.shell(deviceId, 'mount', function (err, output) {
|
332 | if (!err && output.toString().trim().split('\n').some(function (line) {
|
333 | var parts = line.trim().split(' ');
|
334 | return parts.length > 1 && mountPoints.indexOf(parts[1]) !== -1;
|
335 | })) {
|
336 | mounted = true;
|
337 | clearTimeout(sdcardTimer);
|
338 | opts.logger && opts.logger.debug(__('SD card is mounted'));
|
339 | cb();
|
340 | } else {
|
341 | setTimeout(cb, retryTimeout);
|
342 | }
|
343 | });
|
344 | },
|
345 |
|
346 | function () {
|
347 |
|
348 | adb.devices(function (err, devices) {
|
349 | emulib.isRunning(config, emu, devices.filter(d => d.id = emulator.id), function (err, running) {
|
350 | if (!err && running) {
|
351 | appc.util.mix(emulator, running);
|
352 | }
|
353 | emulator.emit('ready', emulator);
|
354 | });
|
355 | });
|
356 | }
|
357 | );
|
358 | });
|
359 | }
|
360 | );
|
361 |
|
362 | sdcardTimer = setTimeout(function () {
|
363 | sdcardTimeout && emulator.emit('timeout', { type: 'sdcard', waited: sdcardTimeout });
|
364 | done = true;
|
365 | }, sdcardTimeout || 30000);
|
366 | });
|
367 | }
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | EmulatorManager.prototype.start = function start(id, opts, callback) {
|
376 | if (opts && typeof opts === 'function') {
|
377 | callback = opts;
|
378 | opts = {};
|
379 | }
|
380 |
|
381 | opts.logger && opts.logger.trace(__('Checking if emulator %s is running...', id.cyan));
|
382 |
|
383 | this.isRunning(id, opts, function (err, running) {
|
384 | if (err) {
|
385 |
|
386 | return callback(err);
|
387 | }
|
388 |
|
389 | if (running) {
|
390 |
|
391 | var emulator = new Emulator();
|
392 | appc.util.mix(emulator, running);
|
393 | opts.logger && opts.logger.info(__('Emulator already running'));
|
394 | checkedBooted(this.config, opts, emulator);
|
395 | callback(null, emulator);
|
396 | return;
|
397 | }
|
398 |
|
399 | opts.logger && opts.logger.trace(__('Emulator not running, detecting emulator info'));
|
400 |
|
401 |
|
402 | this.detect(opts, function (err, emus) {
|
403 | if (err) {
|
404 | return callback(err);
|
405 | }
|
406 |
|
407 | var emu = emus.filter(e => e && e.id == id).shift();
|
408 |
|
409 |
|
410 | if (!emu) {
|
411 | return callback(new Error(__('Invalid emulator "%s"', id)), null);
|
412 | }
|
413 |
|
414 | opts.logger && opts.logger.trace(__('Starting the emulator...'));
|
415 |
|
416 | var emulib = require(path.join(__dirname, 'emulators', emu.type + '.js'));
|
417 | emulib.start(this.config, emu, opts, function (err, emulator) {
|
418 | if (err) {
|
419 | callback(err);
|
420 | } else {
|
421 |
|
422 | opts.logger && opts.logger.trace(__('Emulator is starting, monitoring boot state...'));
|
423 | checkedBooted(this.config, opts, emulator);
|
424 | callback(null, emulator);
|
425 | }
|
426 | }.bind(this));
|
427 | }.bind(this));
|
428 | }.bind(this));
|
429 | };
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 | EmulatorManager.prototype.stop = function stop(id, opts, callback) {
|
438 | if (opts && typeof opts === 'function') {
|
439 | callback = opts;
|
440 | opts = {};
|
441 | }
|
442 |
|
443 | this.isRunning(id, opts, function (err, running) {
|
444 | if (err) {
|
445 |
|
446 | callback(err);
|
447 | } else if (!running) {
|
448 |
|
449 | callback(new Error(__('Emulator "%s" not running', id)));
|
450 | } else {
|
451 | require(path.join(__dirname, 'emulators', running.emulator.type + '.js')).stop(this.config, running.emulator.name, running, opts, callback);
|
452 | }
|
453 | }.bind(this));
|
454 | };
|