const fs = require('fs'); const path = require('path'); const exec = require('./exec'); const {StringWritable} = require('./tail'); const System = require('./system'); const Command = require('../run/command'); const through = require('through2'); const Sdk = require('./sdk'); const Env = require('./env'); class Android extends Sdk { constructor(version) { super(Android.ID, version || Android.VERSION); this.ndk_version = 'r15c'; } get prepared() { try { return fs.existsSync(`${this.path}/tools/bin/sdkmanager`); } catch (e) { return false; } } get link() { const system = System.isWindows ? 'windows' : System.isLinux ? 'linux' : null; if (!system) throw 'Unsupported system'; if (this.version.indexOf('.') > -1) { return `https://dl.google.com/android/repository/tools_r${this.version}-${system}.zip`; } else { return `https://dl.google.com/android/repository/sdk-tools-${system}-${this.version}.zip`; } } get android_home() { return this.path; } get ndk_home() { return path.join(this.path, 'ndk-bundle') } prepare() { const result = []; result.push(super.prepare(0)); if (!fs.existsSync(path.join(this.ndk_home))) { result.push(this.prepare_ndk()); } return Promise.all(result); } prepare_ndk() { // ToDo: support windows const url = `https://dl.google.com/android/repository/android-ndk-${this.ndk_version}-linux-x86_64.zip`; this.log.d('download: *%s*', url); return Sdk.Downloader.download(url, this.ndk_home, 1, true); } sdkmanager(packages) { this.log.i('sdkmanager: *%s*', packages.join(', ')); const androidBin = path.join(this.path, 'tools/bin/sdkmanager'); if (fs.existsSync(androidBin)) { fs.chmodSync(androidBin, 0o755); } const yes = '(while sleep 3; do echo "y"; done)'; return exec('.', [yes, '|', androidBin].concat(packages.map(name => `"${name}"`)).join(' ')); } activate() { Env.set('ANDROID_HOME', this.android_home); Env.set('ANDROID_NDK_HOME', this.ndk_home); Env.set('NDK_HOME', this.ndk_home); } get adbBin() { return path.join(this.path, 'platform-tools/adb'); } adb(args) { return exec('.', [this.adbBin].concat(args).join(' ')).then(data => { for (let line of data.stderr.split('\n')) { if (line.indexOf('Error') > -1) { throw line; } } return data; }); } aapt(args) { let buildToolsVersion = null; fs.readdirSync(path.join(this.path, 'build-tools')).forEach(file => { buildToolsVersion = file; }); const aaptBin = path.join(this.path, 'build-tools', buildToolsVersion, 'aapt'); return exec('.', [aaptBin].concat(args).join(' ')); } apk() { const self = this; return through.obj(function(file, enc, callback) { self.aapt(['l', '-a', file.path]).then(data => { let activity = false; for (let line of data.stdout.split('\n')) { if (line.indexOf('package') > -1) { const value = /"(.*?)"/.exec(line); if (value) file.package = value[1] } if (line.indexOf('activity') > -1) { activity = true; } if (activity && line.indexOf('name') > -1) { const value = /"(.*?)"/.exec(line); if (value) { file.activity = value[1]; activity = false; } } } this.push(file); callback(); }); }); } _installApk(filename, pack) { this.log.i('install *%s*', filename); return this.adb(['install', '-r', '-d', filename]) .then(() => { return false; }) .catch(error => { if (error.includes('INSTALL_FAILED_UPDATE_INCOMPATIBLE')) { this.log.w('!INSTALL FAILED UPDATE INCOMPATIBLE!'); return true; } else { throw error; } }) .then(reinstall => { if (reinstall) { this.log.i('uninstall *%s*', pack); return this.adb(['uninstall', pack]).then(() => { this.log.i('install *%s*', filename); return this.adb(['install', '-r', '-d', filename]); }); } }); } install() { const self = this; return through.obj(function(file, enc, callback) { self._installApk(file.path, file.package).then(() => { this.push(file); callback(); }); }); } start() { const self = this; return through.obj(function(file, enc, callback) { const name = `${file.package}/${file.activity}`; self.log.i('start *%s*', name); self.adb(['shell', 'am', 'start', '-n', name]).then(() => { setTimeout(() => { self.adb(['shell', `"ps | grep ${file.package}"`]).then(result => { file.pid = result.stdout.split(/\s+/)[1]; this.push(file); callback(); }); }, 1000); }); }); } logcat() { const self = this; return through.obj(function(file, enc, callback) { const cmd = `${self.adbBin} logcat --pid ${file.pid}`; const command = new Command(cmd).exec(); this.push(command); command.contents.pipe(new StringWritable(line => { if (line.includes('SDL') && (line.includes('onDestroy()') || line.includes('onStop()'))) { callback(); throw 'Exit'; // ToDo: } })); }); } } Android.ID = 'android'; Android.VERSION_25_2_3 = '25.2.3'; Android.VERSION_3859397 = '3859397'; Android.VERSION_4333796 = '4333796'; Android.VERSION = Android.VERSION_4333796; module.exports = Android;