android-device-manager 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# AndroidDeviceManager
|
|
2
|
+
|
|
3
|
+
## Description ##
|
|
4
|
+
Java desktop app to manage multiple Android devices via adb
|
|
5
|
+
|
|
6
|
+
## Features ##
|
|
7
|
+
- View all connected (and wireless) devices
|
|
8
|
+
- Populates device **phone number, free space, IMEI, carrier** (if available)
|
|
9
|
+
- **Remote control** selected devices (requires [scrcpy](https://github.com/Genymobile/scrcpy))
|
|
10
|
+
- Capture **screenshots** of selected devices
|
|
11
|
+
- **Drag and drop an apk** to **install** on selected devices
|
|
12
|
+
- **Drag and drop a file** to **copy** to selected devices
|
|
13
|
+
- **File Explorer** / Browse filesystem of device
|
|
14
|
+
- download and view folders/files
|
|
15
|
+
- delete folders/files
|
|
16
|
+
- root mode supported
|
|
17
|
+
- View **Device Logs**
|
|
18
|
+
- NOTE: this is a work in progress!
|
|
19
|
+
- **Restart** selected devices
|
|
20
|
+
- Run **user-defined adb commands**
|
|
21
|
+
- Set and display custom properties on each device
|
|
22
|
+
- Start an **adb shell** session with selected devices
|
|
23
|
+
- **View version** of user-defined list of apps
|
|
24
|
+
|
|
25
|
+
## Screenshots ##
|
|
26
|
+
<img src="resources/screenshot-main.jpg" width="600" alt="devices">
|
|
27
|
+
<br>
|
|
28
|
+
<img src="resources/screenshot-mirror.jpg" width="600" alt="devices">
|
|
29
|
+
<br>
|
|
30
|
+
<img src="resources/screenshot-browse.jpg" width="300" alt="file explorer">
|
|
31
|
+
<br>
|
|
32
|
+
<img src="resources/screenshot-logs.jpg" width="600" alt="logs">
|
|
33
|
+
<br>
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Java SDK
|
|
38
|
+
- min version 17; I'm using openjdk 22.0.1 2024-04-16
|
|
39
|
+
- MacOSX -> Homebrew -> `brew install openjdk`
|
|
40
|
+
- Linux - [link](https://www.digitalocean.com/community/tutorials/how-to-install-java-with-apt-on-ubuntu-22-04)
|
|
41
|
+
- **adb**
|
|
42
|
+
- MacOSX -> homebrew -> `brew cask install android-platform-tools`
|
|
43
|
+
- Linux -> `sudo apt install adb fastboot`
|
|
44
|
+
- standalone adb tools can be found [here](https://developer.android.com/tools/releases/platform-tools)
|
|
45
|
+
- **scrcpy** - mirror a connected Android device ([https://github.com/Genymobile/scrcpy](https://github.com/Genymobile/scrcpy))
|
|
46
|
+
- MacOSX -> homebrew -> `brew install scrcpy`
|
|
47
|
+
- Linux -> see [link](https://github.com/Genymobile/scrcpy/blob/master/doc/linux.md#latest-version)
|
|
48
|
+
- make sure both `adb` and `scrcpy` are in the current PATH
|
|
49
|
+
|
|
50
|
+
## Run
|
|
51
|
+
|
|
52
|
+
- Download the latest release from here: [https://github.com/jpage4500/AndroidDeviceManager/releases](https://github.com/jpage4500/AndroidDeviceManager/releases)
|
|
53
|
+
- Mac OSX Users:
|
|
54
|
+
- get the packaged .app version: `AndroidDeviceManager-VERSION-OSX.zip`, extract and move to `/Applications` folder
|
|
55
|
+
- Windows/Linux Users:
|
|
56
|
+
- get the .jar version: `AndroidDeviceManager.jar`
|
|
57
|
+
- run via command-line: `java -jar AndroidDeviceManager.jar`
|
|
58
|
+
|
|
59
|
+
## Build
|
|
60
|
+
|
|
61
|
+
[Build Instructions](BUILD.md)
|
|
62
|
+
|
|
63
|
+
## Use Cases ##
|
|
64
|
+
|
|
65
|
+
We want to manage a lot of Android devices and had previously used MDM (mobile device management) software such as **AirDroid** and **ScaleFusion**. These tools aren't free ($$) but more importantly trying to remote control/view an Android device was often a very slow and choppy experience.
|
|
66
|
+
|
|
67
|
+
So, instead we took a different approach. Instead of running MDM software on every individual Android device, we connected all of the devices to a single macbook laptop using multiple 16-port USB hubs. The Macbook is running [Splashtop](https://www.splashtop.com/) remote control software. I can now remote login and using Android Device Manager control all of the devices with very little to no lag.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
Tested with 45 Android devices connected to 1 Macbook laptop (using multiple 16-port USB hubs)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
|
|
3
|
+
var jarName = "AndroidDeviceManager.jar";
|
|
4
|
+
var mainClass = "{{MAIN_CLASS}}";
|
|
5
|
+
var classPath = "{{CLASSPATH}}";
|
|
6
|
+
var port = "0";
|
|
7
|
+
var warPath = "";
|
|
8
|
+
var javaVersionString = "22";
|
|
9
|
+
var tryJavaHomeFirst = false;
|
|
10
|
+
var javafx = false;
|
|
11
|
+
var bundleType = 'jre';
|
|
12
|
+
if ('{{JAVAFX}}' === 'true') {
|
|
13
|
+
javafx = true;
|
|
14
|
+
}
|
|
15
|
+
if ('{{JDK}}' === 'true') {
|
|
16
|
+
bundleType = 'jdk';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var jdk = (bundleType === 'jdk');
|
|
20
|
+
var jdkProvider = 'zulu';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
function njreWrap() {
|
|
24
|
+
'use strict'
|
|
25
|
+
|
|
26
|
+
const path = require('path')
|
|
27
|
+
const fs = require('fs')
|
|
28
|
+
const os = require('os')
|
|
29
|
+
const crypto = require('crypto')
|
|
30
|
+
const fetch = require('node-fetch')
|
|
31
|
+
const yauzl = require('yauzl')
|
|
32
|
+
const tar = require('tar')
|
|
33
|
+
|
|
34
|
+
function createDir (dir) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
fs.access(dir, err => {
|
|
37
|
+
if (err && err.code === 'ENOENT') {
|
|
38
|
+
fs.mkdir(dir, err => {
|
|
39
|
+
if (err) reject(err)
|
|
40
|
+
resolve()
|
|
41
|
+
})
|
|
42
|
+
} else if (!err) resolve()
|
|
43
|
+
else reject(err)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function download (dir, url) {
|
|
49
|
+
if (url.indexOf("?") > 0 || jdkProvider === 'zulu') {
|
|
50
|
+
var ext = ".zip";
|
|
51
|
+
switch (process.platform) {
|
|
52
|
+
case 'linux':
|
|
53
|
+
ext = ".tar.gz";
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
var destName = bundleType + ext;
|
|
57
|
+
} else {
|
|
58
|
+
destName = path.basename(url);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
createDir(dir)
|
|
63
|
+
.then(() => fetch(url))
|
|
64
|
+
.then(response => {
|
|
65
|
+
const destFile = path.join(dir, destName)
|
|
66
|
+
const destStream = fs.createWriteStream(destFile)
|
|
67
|
+
response.body.pipe(destStream).on('finish', () => resolve(destFile))
|
|
68
|
+
})
|
|
69
|
+
.catch(err => reject(err))
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function downloadAll (dir, url) {
|
|
74
|
+
return download(dir, url + '.sha256.txt').then(() => download(dir, url))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function genChecksum (file) {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
fs.readFile(file, (err, data) => {
|
|
80
|
+
if (err) reject(err)
|
|
81
|
+
|
|
82
|
+
resolve(
|
|
83
|
+
crypto
|
|
84
|
+
.createHash('sha256')
|
|
85
|
+
.update(data)
|
|
86
|
+
.digest('hex')
|
|
87
|
+
)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function verify (file) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
fs.readFile(file + '.sha256.txt', 'utf-8', (err, data) => {
|
|
95
|
+
if (err) reject(err)
|
|
96
|
+
|
|
97
|
+
genChecksum(file).then(checksum => {
|
|
98
|
+
checksum === data.split(' ')[0]
|
|
99
|
+
? resolve(file)
|
|
100
|
+
: reject(new Error('File and checksum don\'t match'))
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function move (file) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const jdeployDir = path.join(os.homedir(), '.jdeploy');
|
|
109
|
+
if (!fs.existsSync(jdeployDir)) {
|
|
110
|
+
fs.mkdirSync(jdeployDir);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var jreDir = path.join(jdeployDir, bundleType);
|
|
114
|
+
if (!fs.existsSync(jreDir)) {
|
|
115
|
+
fs.mkdirSync(jreDir);
|
|
116
|
+
}
|
|
117
|
+
var vs = javaVersionString;
|
|
118
|
+
if (javafx) {
|
|
119
|
+
vs += 'fx';
|
|
120
|
+
}
|
|
121
|
+
jreDir = path.join(jreDir, vs);
|
|
122
|
+
if (!fs.existsSync(jreDir)) {
|
|
123
|
+
fs.mkdirSync(jreDir);
|
|
124
|
+
}
|
|
125
|
+
const newFile = path.join(jreDir, file.split(path.sep).slice(-1)[0])
|
|
126
|
+
//console.log("Copying file "+file+" to "+newFile);
|
|
127
|
+
fs.copyFile(file, newFile, err => {
|
|
128
|
+
if (err) reject(err)
|
|
129
|
+
|
|
130
|
+
fs.unlink(file, err => {
|
|
131
|
+
if (err) reject(err)
|
|
132
|
+
resolve(newFile)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function extractZip (file, dir) {
|
|
139
|
+
//console.log("Extracting "+file+" to "+dir);
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
yauzl.open(file, { lazyEntries: true }, (err, zipFile) => {
|
|
142
|
+
if (err) reject(err)
|
|
143
|
+
|
|
144
|
+
zipFile.readEntry()
|
|
145
|
+
zipFile.on('entry', entry => {
|
|
146
|
+
const entryPath = path.join(dir, entry.fileName)
|
|
147
|
+
|
|
148
|
+
if (/\/$/.test(entry.fileName)) {
|
|
149
|
+
fs.mkdir(entryPath, { recursive: true }, err => {
|
|
150
|
+
if (err && err.code !== 'EEXIST') reject(err)
|
|
151
|
+
|
|
152
|
+
zipFile.readEntry()
|
|
153
|
+
})
|
|
154
|
+
} else {
|
|
155
|
+
zipFile.openReadStream(entry, (err, readStream) => {
|
|
156
|
+
if (err) reject(err)
|
|
157
|
+
|
|
158
|
+
readStream.on('end', () => {
|
|
159
|
+
zipFile.readEntry()
|
|
160
|
+
})
|
|
161
|
+
readStream.pipe(fs.createWriteStream(entryPath))
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
zipFile.once('close', () => {
|
|
166
|
+
fs.unlink(file, err => {
|
|
167
|
+
if (err) reject(err)
|
|
168
|
+
resolve(dir)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function extractTarGz (file, dir) {
|
|
176
|
+
return tar.x({ file: file, cwd: dir }).then(() => {
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
fs.unlink(file, err => {
|
|
179
|
+
if (err) reject(err)
|
|
180
|
+
resolve(dir)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function extract (file) {
|
|
187
|
+
var dirString = jdk? 'jdk' : 'jre';
|
|
188
|
+
|
|
189
|
+
const dir = path.join(path.dirname(file), dirString)
|
|
190
|
+
//console.log("About to extract "+file+" to "+dir);
|
|
191
|
+
return createDir(dir).then(() => {
|
|
192
|
+
return path.extname(file) === '.zip'
|
|
193
|
+
? extractZip(file, dir)
|
|
194
|
+
: extractTarGz(file, dir)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Installs a JRE copy for the app
|
|
200
|
+
* @param {number} [version = 8] - Java Version (`8`/`9`/`10`/`11`/`12`)
|
|
201
|
+
* @param {object} [options] - Installation Options
|
|
202
|
+
* @param {string} [options.os] - Operating System (defaults to current) (`windows`/`mac`/`linux`/`solaris`/`aix`)
|
|
203
|
+
* @param {string} [options.arch] - Architecture (defaults to current) (`x64`/`x32`/`ppc64`/`s390x`/`ppc64le`/`aarch64`/`sparcv9`)
|
|
204
|
+
* @param {string} [options.openjdk_impl = hotspot] - OpenJDK Implementation (`hotspot`/`openj9`)
|
|
205
|
+
* @param {string} [options.release = latest] - Release
|
|
206
|
+
* @param {string} [options.type = jre] - Binary Type (`jre`/`jdk`)
|
|
207
|
+
* @param {string} [options.heap_size] - Heap Size (`normal`/`large`)
|
|
208
|
+
* @return Promise<string> - Resolves to the installation directory or rejects an error
|
|
209
|
+
* @example
|
|
210
|
+
* const njre = require('njre')
|
|
211
|
+
*
|
|
212
|
+
* // Use default options
|
|
213
|
+
* njre.install()
|
|
214
|
+
* .then(dir => {
|
|
215
|
+
* // Do stuff
|
|
216
|
+
* })
|
|
217
|
+
* .catch(err => {
|
|
218
|
+
* // Handle the error
|
|
219
|
+
* })
|
|
220
|
+
*
|
|
221
|
+
* // or custom ones
|
|
222
|
+
* njre.install(11, { os: 'aix', arch: 'ppc64', openjdk_impl: 'openj9' })
|
|
223
|
+
* .then(dir => {
|
|
224
|
+
* // Do stuff
|
|
225
|
+
* })
|
|
226
|
+
* .catch(err => {
|
|
227
|
+
* // Handle the error
|
|
228
|
+
* })
|
|
229
|
+
*/
|
|
230
|
+
function install (version = 11, options = {}) {
|
|
231
|
+
const { openjdk_impl = 'hotspot', release = 'latest', type = 'jre', javafx = false, provider = 'zulu' } = options
|
|
232
|
+
options = { ...options, openjdk_impl, release, type }
|
|
233
|
+
|
|
234
|
+
if (provider === 'zulu') {
|
|
235
|
+
return installZulu(version, options);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let url = 'https://api.adoptopenjdk.net/v2/info/releases/openjdk' + version + '?'
|
|
239
|
+
|
|
240
|
+
if (!options.os) {
|
|
241
|
+
switch (process.platform) {
|
|
242
|
+
case 'aix':
|
|
243
|
+
options.os = 'aix'
|
|
244
|
+
break
|
|
245
|
+
case 'darwin':
|
|
246
|
+
options.os = 'mac'
|
|
247
|
+
break
|
|
248
|
+
case 'linux':
|
|
249
|
+
options.os = 'linux'
|
|
250
|
+
break
|
|
251
|
+
case 'sunos':
|
|
252
|
+
options.os = 'solaris'
|
|
253
|
+
break
|
|
254
|
+
case 'win32':
|
|
255
|
+
options.os = 'windows'
|
|
256
|
+
break
|
|
257
|
+
default:
|
|
258
|
+
return Promise.reject(new Error('Unsupported operating system'))
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (!options.arch) {
|
|
262
|
+
if (options.os == 'mac') {
|
|
263
|
+
// For now, for compatibility reasons use x64 always
|
|
264
|
+
options.arch = 'x64';
|
|
265
|
+
} else if (/^ppc64|s390x|x32|x64$/g.test(process.arch)) options.arch = process.arch
|
|
266
|
+
else if (process.arch === 'ia32') options.arch = 'x32'
|
|
267
|
+
else return Promise.reject(new Error('Unsupported architecture'))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Object.keys(options).forEach(key => { url += key + '=' + options[key] + '&' })
|
|
271
|
+
|
|
272
|
+
const tmpdir = path.join(os.tmpdir(), 'njre')
|
|
273
|
+
|
|
274
|
+
return fetch(url)
|
|
275
|
+
.then(response => response.json())
|
|
276
|
+
.then(json => downloadAll(tmpdir, json.binaries[0]['binary_link']))
|
|
277
|
+
.then(verify)
|
|
278
|
+
.then(move)
|
|
279
|
+
.then(extract)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function installZulu(version = 11, options = {}) {
|
|
283
|
+
const { type = 'jre', javafx = false } = options
|
|
284
|
+
var q = {
|
|
285
|
+
|
|
286
|
+
java_version: version,
|
|
287
|
+
ext: 'zip',
|
|
288
|
+
bundle_type: type,
|
|
289
|
+
javafx: ''+javafx,
|
|
290
|
+
arch: 'x86',
|
|
291
|
+
hw_bitness: '64',
|
|
292
|
+
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
var zuluBaseURL = "https://api.azul.com/zulu/download/community/v1.0/bundles/latest/binary?"
|
|
297
|
+
if (!options.os) {
|
|
298
|
+
switch (process.platform) {
|
|
299
|
+
|
|
300
|
+
case 'darwin':
|
|
301
|
+
q.os = 'macos'
|
|
302
|
+
break
|
|
303
|
+
case 'linux':
|
|
304
|
+
q.os = 'linux'
|
|
305
|
+
q.ext = 'tar.gz'
|
|
306
|
+
break
|
|
307
|
+
|
|
308
|
+
case 'win32':
|
|
309
|
+
case 'win64':
|
|
310
|
+
q.os = 'windows'
|
|
311
|
+
break
|
|
312
|
+
default:
|
|
313
|
+
return Promise.reject(new Error('Unsupported operating system'))
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
var url = zuluBaseURL;
|
|
319
|
+
Object.keys(q).forEach(key => { url += key + '=' + q[key] + '&' })
|
|
320
|
+
const tmpdir = path.join(os.tmpdir(), 'njre')
|
|
321
|
+
//console.log("Downloading "+url);
|
|
322
|
+
return download(tmpdir, url)
|
|
323
|
+
.then(move)
|
|
324
|
+
.then(extract)
|
|
325
|
+
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {install:install};
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
var fs = require('fs');
|
|
336
|
+
var os = require('os');
|
|
337
|
+
var path = require('path');
|
|
338
|
+
const njre = njreWrap();
|
|
339
|
+
const targetJavaVersion = parseInt(javaVersionString);
|
|
340
|
+
var shell = require("shelljs/global");
|
|
341
|
+
function getJdeploySupportDir() {
|
|
342
|
+
return os.homedir() + path.sep + ".jdeploy";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function getJavaVersion(binPath) {
|
|
346
|
+
|
|
347
|
+
var oldPath = env['PATH'];
|
|
348
|
+
if (binPath) {
|
|
349
|
+
env['PATH'] = binPath + path.delimiter + env['PATH'];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
var javaVersionProc = exec('java -version', {silent:true});
|
|
354
|
+
if (javaVersionProc.code !== 0) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
var stdout = javaVersionProc.stderr;
|
|
358
|
+
var regexp = /version "(.*?)"/;
|
|
359
|
+
var match = regexp.exec(stdout);
|
|
360
|
+
var parts = match[1].split('.');
|
|
361
|
+
var join = '.';
|
|
362
|
+
var versionStr = '';
|
|
363
|
+
parts.forEach(function(v) {
|
|
364
|
+
versionStr += v;
|
|
365
|
+
if (join !== null) {
|
|
366
|
+
versionStr += join;
|
|
367
|
+
join = null;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
versionStr = versionStr.replace('_', '');
|
|
371
|
+
return parseFloat(versionStr);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
return false;
|
|
374
|
+
} finally {
|
|
375
|
+
env['PATH'] = oldPath;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
var getDirectories = dirPath => fs.readdirSync(dirPath).filter(
|
|
379
|
+
file => fs.statSync(path.join(dirPath, file)).isDirectory()
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
function getJavaHomeInPath(basepath) {
|
|
383
|
+
|
|
384
|
+
var dirs = null;
|
|
385
|
+
try {
|
|
386
|
+
dirs = getDirectories(basepath);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
if (dirs && dirs.length > 0) {
|
|
391
|
+
basepath = path.join(basepath, dirs[0]);
|
|
392
|
+
if (os.platform() != 'darwin') {
|
|
393
|
+
return basepath;
|
|
394
|
+
}
|
|
395
|
+
if (fs.existsSync(path.join(basepath, 'Contents', 'Home'))) {
|
|
396
|
+
return path.join(basepath, 'Contents', 'Home');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
var adapterDirectories = getDirectories(basepath).filter(subdir => {
|
|
400
|
+
return subdir.match(/^zulu/) && fs.existsSync(path.join(basepath, subdir, 'Contents', 'Home'));
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
if (adapterDirectories && adapterDirectories.length > 0) {
|
|
404
|
+
return path.join(basepath, adapterDirectories[0], 'Contents', 'Home');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function findSupportedRuntime(javaVersion, jdk, javafx) {
|
|
411
|
+
var jdeployDir = path.join(os.homedir(), ".jdeploy");
|
|
412
|
+
var JAVA_HOME_OVERRIDE = env['JDEPLOY_JAVA_HOME_OVERRIDE'];
|
|
413
|
+
|
|
414
|
+
if (JAVA_HOME_OVERRIDE && fs.existsSync(JAVA_HOME_OVERRIDE)) {
|
|
415
|
+
return JAVA_HOME_OVERRIDE;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// First check for the full-meal deal
|
|
419
|
+
var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jdk', javaVersion+'fx', 'jdk'));
|
|
420
|
+
if (_javaHomePath && fs.existsSync(_javaHomePath)) {
|
|
421
|
+
return _javaHomePath;
|
|
422
|
+
}
|
|
423
|
+
if (!javafx) {
|
|
424
|
+
var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jdk', javaVersion, 'jdk'));
|
|
425
|
+
if (_javaHomePath && fs.existsSync(_javaHomePath)) {
|
|
426
|
+
return _javaHomePath;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!jdk) {
|
|
431
|
+
var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jre', javaVersion+'fx', 'jre'));
|
|
432
|
+
if (_javaHomePath && fs.existsSync(_javaHomePath)) {
|
|
433
|
+
return _javaHomePath;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!jdk && !javafx) {
|
|
438
|
+
var _javaHomePath = getJavaHomeInPath(path.join(jdeployDir, 'jre', javaVersion, 'jre'));
|
|
439
|
+
if (_javaHomePath && fs.existsSync(_javaHomePath)) {
|
|
440
|
+
return _javaHomePath;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getEmbeddedJavaHome() {
|
|
448
|
+
var _platform = os.platform();
|
|
449
|
+
var _driver = '';
|
|
450
|
+
switch (_platform) {
|
|
451
|
+
case 'darwin': _platform = 'macosx'; _driver = 'Contents' + path.sep + 'Home'; break;
|
|
452
|
+
case 'win32': _platform = 'windows'; _driver = ''; break;
|
|
453
|
+
case 'linux': _driver = ''; break;
|
|
454
|
+
default:
|
|
455
|
+
fail('unsupported platform: ' + _platform);
|
|
456
|
+
}
|
|
457
|
+
var vs = javaVersionString;
|
|
458
|
+
if (javafx) {
|
|
459
|
+
vs += 'fx';
|
|
460
|
+
}
|
|
461
|
+
var typeDir = jdk ? 'jdk' : 'jre';
|
|
462
|
+
|
|
463
|
+
var jreDir = path.join(os.homedir(), '.jdeploy', 'jre', vs, 'jre');
|
|
464
|
+
try {
|
|
465
|
+
var out = jreDir + path.sep + getDirectories(jreDir)[0] + (_driver ? (path.sep + _driver) : '');
|
|
466
|
+
return out;
|
|
467
|
+
} catch (e) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function javaVersionMatch(v1, v2) {
|
|
473
|
+
if (v1 === 8) v1 = 1.8;
|
|
474
|
+
if (v2 === 8) v2 = 1.8;
|
|
475
|
+
if (Math.floor(v1) !== Math.floor(v2)) {
|
|
476
|
+
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
if (v1 < 2) {
|
|
480
|
+
// Up to 1.8, the version would be like 1.7, 1.8, etc..
|
|
481
|
+
// So we need to check the minor version for equivalency
|
|
482
|
+
return (Math.floor(v1*10) === Math.floor(v2*10));
|
|
483
|
+
} else {
|
|
484
|
+
// Starting with Java 9, the version is like 9, 10, 11, etc..
|
|
485
|
+
// so we just compare major version.
|
|
486
|
+
return (Math.floor(v1) === Math.floor(v2));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
var done = false;
|
|
492
|
+
if (tryJavaHomeFirst) {
|
|
493
|
+
if (env['JAVA_HOME']) {
|
|
494
|
+
var javaHomeVersion = getJavaVersion(path.join(env['JAVA_HOME'], 'bin'));
|
|
495
|
+
if (javaVersionMatch(javaHomeVersion, targetJavaVersion)) {
|
|
496
|
+
done = true;
|
|
497
|
+
env['PATH'] = path.join(env['JAVA_HOME'], 'bin') + path.delimiter + env['PATH'];
|
|
498
|
+
run(env['JAVA_HOME']);
|
|
499
|
+
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!done) {
|
|
504
|
+
var javaVersion = getJavaVersion();
|
|
505
|
+
if (javaVersionMatch(javaVersion, targetJavaVersion)) {
|
|
506
|
+
done = true;
|
|
507
|
+
run();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
if (!done) {
|
|
514
|
+
|
|
515
|
+
var _javaHome = findSupportedRuntime(javaVersionString, bundleType === 'jdk', javafx);
|
|
516
|
+
if (_javaHome && fs.existsSync(_javaHome)) {
|
|
517
|
+
var javaVersion = getJavaVersion(path.join(_javaHome, 'bin'));
|
|
518
|
+
if (javaVersionMatch(javaVersion, targetJavaVersion)) {
|
|
519
|
+
env['PATH'] = path.join(_javaHome, 'bin') + path.delimiter + env['PATH'];
|
|
520
|
+
env['JAVA_HOME'] = _javaHome;
|
|
521
|
+
done = true;
|
|
522
|
+
run(_javaHome);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (!done) {
|
|
529
|
+
console.log("Downloading java runtime environment for version "+targetJavaVersion);
|
|
530
|
+
njre.install(targetJavaVersion, {type: bundleType, javafx: javafx}).then(function(dir) {
|
|
531
|
+
var _javaHome = getJavaHomeInPath(dir);
|
|
532
|
+
if (_javaHome == null)
|
|
533
|
+
|
|
534
|
+
if (!_javaHome || !fs.existsSync(_javaHome)) {
|
|
535
|
+
throw new Error("After install, could not find java home at "+_javaHome);
|
|
536
|
+
}
|
|
537
|
+
env['JAVA_HOME'] = _javaHome;
|
|
538
|
+
|
|
539
|
+
var javaBinary = path.join(_javaHome, 'bin', 'java');
|
|
540
|
+
if (!fs.existsSync(javaBinary)) {
|
|
541
|
+
javaBinary += '.exe';
|
|
542
|
+
|
|
543
|
+
}
|
|
544
|
+
fs.chmodSync(javaBinary, 0o755);
|
|
545
|
+
|
|
546
|
+
env['PATH'] = path.join(env['JAVA_HOME'], 'bin') + path.delimiter + env['PATH'];
|
|
547
|
+
|
|
548
|
+
run(env['JAVA_HOME']);
|
|
549
|
+
}).catch(function(err) {
|
|
550
|
+
console.log("Failed to install JRE", err);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
function run(_javaHome) {
|
|
558
|
+
var fail = reason => {
|
|
559
|
+
console.error(reason);
|
|
560
|
+
process.exit(1);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
classPath = classPath.split(':');
|
|
565
|
+
var classPathStr = '';
|
|
566
|
+
var first = true;
|
|
567
|
+
classPath.forEach(function(part) {
|
|
568
|
+
if (!first) classPathStr += path.delimiter;
|
|
569
|
+
first = false;
|
|
570
|
+
classPathStr += __dirname + '/' + part;
|
|
571
|
+
});
|
|
572
|
+
classPath = classPathStr;
|
|
573
|
+
|
|
574
|
+
var userArgs = process.argv.slice(2);
|
|
575
|
+
var javaArgs = [];
|
|
576
|
+
javaArgs.push('-Djdeploy.base='+__dirname);
|
|
577
|
+
javaArgs.push('-Djdeploy.port='+port);
|
|
578
|
+
javaArgs.push('-Djdeploy.war.path='+warPath);
|
|
579
|
+
var programArgs = [];
|
|
580
|
+
userArgs.forEach(function(arg) {
|
|
581
|
+
if (arg.startsWith('-D') || arg.startsWith('-X')) {
|
|
582
|
+
javaArgs.push(arg);
|
|
583
|
+
} else {
|
|
584
|
+
programArgs.push(arg);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
var cmd = 'java';
|
|
588
|
+
|
|
589
|
+
if (!_javaHome) {
|
|
590
|
+
env['PATH'] = path.join(getEmbeddedJavaHome(), 'bin') + path.delimiter + env['PATH'];
|
|
591
|
+
if (env['JAVA_HOME']) {
|
|
592
|
+
env['PATH'] = env['JAVA_HOME'] + path.sep + 'bin' + path.delimiter + env['PATH'];
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
} else {
|
|
596
|
+
env['JAVA_HOME'] = _javaHome;
|
|
597
|
+
cmd = _javaHome + path.sep + 'bin' + path.sep + 'java';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
javaArgs.forEach(function(arg) {
|
|
601
|
+
cmd += ' "'+arg+'"';
|
|
602
|
+
});
|
|
603
|
+
if (jarName !== '{'+'{JAR_NAME}}') {
|
|
604
|
+
cmd += ' -jar "'+__dirname+'/'+jarName+'" ';
|
|
605
|
+
} else {
|
|
606
|
+
cmd += ' -cp "'+classPath+'" '+mainClass+' ';
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
programArgs.forEach(function(arg) {
|
|
610
|
+
cmd += ' "'+arg+'"';
|
|
611
|
+
});
|
|
612
|
+
var child = exec(cmd, {async: true});
|
|
613
|
+
process.stdin.setEncoding('utf8');
|
|
614
|
+
|
|
615
|
+
process.stdin.on('readable', function() {
|
|
616
|
+
var chunk = null;
|
|
617
|
+
while (null !== (chunk = process.stdin.read())) {
|
|
618
|
+
try {
|
|
619
|
+
child.stdin.write(chunk);
|
|
620
|
+
} catch(e){}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
child.on('close', function(code) {
|
|
624
|
+
process.exit(code);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"bin":{"AndroidDeviceManager":"jdeploy-bundle/jdeploy.js"},"author":"Joe Page","description":"Java desktop app to manage multiple Android devices via adb","main":"index.js","preferGlobal":true,"repository":"","version":"1.0.0","jdeploy":{"jdk":false,"checksums":{"icon.png":"4b08e47a91c363c29f10aa1ca9a9ce55","installsplash.png":"4b08e47a91c363c29f10aa1ca9a9ce55"},"javaVersion":"22","documentTypes":[{"extension":"apk"}],"jar":"target/AndroidDeviceManager.jar","javafx":false,"title":"AndroidDeviceManager"},"dependencies":{"command-exists-promise":"^2.0.2","node-fetch":"2.6.7","tar":"^4.4.8","yauzl":"^2.10.0","shelljs":"^0.8.4"},"license":"ISC","name":"android-device-manager","files":["jdeploy-bundle"],"scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"homepage":""}
|