neoagent 2.3.1-beta.88 → 2.3.1-beta.89
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/flutter_app/web/index.html +0 -1
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/index.html +0 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/android/controller.js +260 -89
- package/server/services/runtime/backends/local-vm.js +8 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
565ea9475bb1a5c2efca6dc427d87b38
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "2533429045" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
package/server/public/index.html
CHANGED
|
@@ -128078,7 +128078,7 @@ r===$&&A.b()
|
|
|
128078
128078
|
o.push(A.ih(p,A.iW(!1,new A.a3(B.tI,A.dT(new A.cI(B.hb,new A.a5t(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
|
|
128079
128079
|
if(!s.ay)if(!s.ch){r=s.e
|
|
128080
128080
|
r===$&&A.b()
|
|
128081
|
-
r=B.b.t("
|
|
128081
|
+
r=B.b.t("mp5t17sf-b851db3").length!==0&&r.b}if(r){r=s.d
|
|
128082
128082
|
r===$&&A.b()
|
|
128083
128083
|
r=r.V&&!r.a0?84:0
|
|
128084
128084
|
q=s.e
|
|
@@ -132854,7 +132854,7 @@ $S:273}
|
|
|
132854
132854
|
A.Y8.prototype={}
|
|
132855
132855
|
A.R7.prototype={
|
|
132856
132856
|
mJ(a){var s=this
|
|
132857
|
-
if(B.b.t("
|
|
132857
|
+
if(B.b.t("mp5t17sf-b851db3").length===0||s.a!=null)return
|
|
132858
132858
|
s.zY()
|
|
132859
132859
|
s.a=A.pR(B.PA,new A.b4b(s))},
|
|
132860
132860
|
zY(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -132872,7 +132872,7 @@ if(!t.f.b(k)){s=1
|
|
|
132872
132872
|
break}i=J.Z(k,"buildId")
|
|
132873
132873
|
h=i==null?null:B.b.t(J.r(i))
|
|
132874
132874
|
j=h==null?"":h
|
|
132875
|
-
if(J.bk(j)===0||J.d(j,"
|
|
132875
|
+
if(J.bk(j)===0||J.d(j,"mp5t17sf-b851db3")){s=1
|
|
132876
132876
|
break}n.b=!0
|
|
132877
132877
|
n.J()
|
|
132878
132878
|
p=2
|
|
@@ -132889,7 +132889,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
132889
132889
|
return A.k($async$zY,r)},
|
|
132890
132890
|
v0(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
132891
132891
|
var $async$v0=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
132892
|
-
s=p}for(;;)switch(s){case 0:if(B.b.t("
|
|
132892
|
+
s=p}for(;;)switch(s){case 0:if(B.b.t("mp5t17sf-b851db3").length===0||n.c){s=1
|
|
132893
132893
|
break}n.c=!0
|
|
132894
132894
|
n.J()
|
|
132895
132895
|
p=4
|
|
@@ -53,9 +53,16 @@ for (const dir of [ANDROID_ROOT, SDK_ROOT, EMULATOR_HOME, ARTIFACTS_DIR, SCREENS
|
|
|
53
53
|
|
|
54
54
|
function ensureEmulatorAdvancedFeaturesFile() {
|
|
55
55
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
fs.mkdirSync(path.dirname(EMULATOR_ADVANCED_FEATURES_FILE), { recursive: true });
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
EMULATOR_ADVANCED_FEATURES_FILE,
|
|
59
|
+
[
|
|
60
|
+
'QuickbootFileBacked=off',
|
|
61
|
+
'QuickbootSupport=off',
|
|
62
|
+
'',
|
|
63
|
+
].join('\n'),
|
|
64
|
+
'utf8',
|
|
65
|
+
);
|
|
59
66
|
} catch {}
|
|
60
67
|
}
|
|
61
68
|
|
|
@@ -200,6 +207,15 @@ function tailFile(filePath, maxLines = 40) {
|
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
|
|
210
|
+
function isLikelyPng(buffer) {
|
|
211
|
+
return Buffer.isBuffer(buffer)
|
|
212
|
+
&& buffer.length > 24
|
|
213
|
+
&& buffer[0] === 0x89
|
|
214
|
+
&& buffer[1] === 0x50
|
|
215
|
+
&& buffer[2] === 0x4e
|
|
216
|
+
&& buffer[3] === 0x47;
|
|
217
|
+
}
|
|
218
|
+
|
|
203
219
|
function commandExists(command) {
|
|
204
220
|
const probe = spawnSync('bash', ['-lc', `command -v "${command}"`], { encoding: 'utf8' });
|
|
205
221
|
return probe.status === 0;
|
|
@@ -271,6 +287,9 @@ function emulatorGpuMode() {
|
|
|
271
287
|
if (process.platform === 'darwin' && process.arch === 'arm64') {
|
|
272
288
|
return 'swiftshader_indirect';
|
|
273
289
|
}
|
|
290
|
+
if (process.platform === 'linux' && process.arch === 'arm64') {
|
|
291
|
+
return 'swiftshader_indirect';
|
|
292
|
+
}
|
|
274
293
|
return 'auto';
|
|
275
294
|
}
|
|
276
295
|
|
|
@@ -292,6 +311,8 @@ function isRecoverableEmulatorStartError(message) {
|
|
|
292
311
|
return (
|
|
293
312
|
value.includes('failed to restore previous context') ||
|
|
294
313
|
value.includes('emulator exited before boot completed') ||
|
|
314
|
+
value.includes('android framework did not become ready') ||
|
|
315
|
+
value.includes('package manager service did not become ready') ||
|
|
295
316
|
value.includes('failed to process .ini file') ||
|
|
296
317
|
value.includes('error while loading state for instance')
|
|
297
318
|
);
|
|
@@ -437,10 +458,10 @@ function hostAndroidSdkRoot() {
|
|
|
437
458
|
return null;
|
|
438
459
|
}
|
|
439
460
|
|
|
440
|
-
const SHARED_ANDROID_SDK_ROOT =
|
|
461
|
+
const SHARED_ANDROID_SDK_ROOT = null;
|
|
441
462
|
|
|
442
463
|
function activeAndroidSdkRoot() {
|
|
443
|
-
return
|
|
464
|
+
return SDK_ROOT;
|
|
444
465
|
}
|
|
445
466
|
|
|
446
467
|
function sharedAndroidSdkReady() {
|
|
@@ -458,20 +479,6 @@ function adbBinary() {
|
|
|
458
479
|
if (process.env.ANDROID_ADB_PATH) {
|
|
459
480
|
return process.env.ANDROID_ADB_PATH;
|
|
460
481
|
}
|
|
461
|
-
if (SHARED_ANDROID_SDK_ROOT) {
|
|
462
|
-
const sharedAdb = path.join(SHARED_ANDROID_SDK_ROOT, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
463
|
-
if (isExecutable(sharedAdb)) {
|
|
464
|
-
return sharedAdb;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
const hostAdb = hostAdbBinary();
|
|
468
|
-
if (hostAdb) {
|
|
469
|
-
return hostAdb;
|
|
470
|
-
}
|
|
471
|
-
const distroAdb = systemAdbBinary();
|
|
472
|
-
if (process.platform === 'linux' && process.arch === 'arm64' && distroAdb && isExecutable(distroAdb)) {
|
|
473
|
-
return distroAdb;
|
|
474
|
-
}
|
|
475
482
|
return path.join(SDK_ROOT, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
476
483
|
}
|
|
477
484
|
|
|
@@ -819,17 +826,8 @@ async function installPlatformToolsArchive(metadata) {
|
|
|
819
826
|
}
|
|
820
827
|
|
|
821
828
|
function shouldInstallPlatformToolsArchive() {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
}
|
|
825
|
-
if (hostAdbBinary()) {
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
const distroAdb = systemAdbBinary();
|
|
829
|
-
if (process.platform === 'linux' && process.arch === 'arm64' && distroAdb && isExecutable(distroAdb)) {
|
|
830
|
-
return false;
|
|
831
|
-
}
|
|
832
|
-
return true;
|
|
829
|
+
const localAdb = path.join(SDK_ROOT, 'platform-tools', process.platform === 'win32' ? 'adb.exe' : 'adb');
|
|
830
|
+
return !isExecutable(localAdb);
|
|
833
831
|
}
|
|
834
832
|
|
|
835
833
|
async function installSystemImageArchive(metadata, packageName) {
|
|
@@ -947,27 +945,30 @@ function parseInstalledSystemImages() {
|
|
|
947
945
|
return parseSystemImageCandidates(matches);
|
|
948
946
|
}
|
|
949
947
|
|
|
948
|
+
function chooseStableRuntimeSystemImage(candidates, currentPackage) {
|
|
949
|
+
const normalizedCurrent = String(currentPackage || '').trim();
|
|
950
|
+
const pool = Array.isArray(candidates)
|
|
951
|
+
? candidates.filter((item) => item && item.packageName && isValidInstalledSystemImage(item.packageName))
|
|
952
|
+
: [];
|
|
953
|
+
if (pool.length === 0) return null;
|
|
954
|
+
const ranked = rankSystemImagePool(pool);
|
|
955
|
+
const recommended = ranked.find((item) =>
|
|
956
|
+
item.arch === 'arm64-v8a'
|
|
957
|
+
&& item.stable
|
|
958
|
+
&& item.apiLevel >= 33
|
|
959
|
+
) || ranked[0];
|
|
960
|
+
if (!recommended) return null;
|
|
961
|
+
if (normalizedCurrent && recommended.packageName === normalizedCurrent) {
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
return recommended;
|
|
965
|
+
}
|
|
966
|
+
|
|
950
967
|
function rankSystemImagePool(pool) {
|
|
951
968
|
const preferredMatches = pool.filter((candidate) => candidate.tagScore > 0);
|
|
952
969
|
const rankedPool = preferredMatches.length > 0 ? preferredMatches : pool;
|
|
953
|
-
const appleSiliconPreferredApis = process.platform === 'darwin' && process.arch === 'arm64'
|
|
954
|
-
? [30, 31, 35, 36]
|
|
955
|
-
: [];
|
|
956
|
-
const preferredApiRank = (apiLevel) => appleSiliconPreferredApis.indexOf(Number(apiLevel || 0));
|
|
957
970
|
|
|
958
971
|
rankedPool.sort((a, b) =>
|
|
959
|
-
(
|
|
960
|
-
(() => {
|
|
961
|
-
const aRank = preferredApiRank(a.apiLevel);
|
|
962
|
-
const bRank = preferredApiRank(b.apiLevel);
|
|
963
|
-
if (aRank !== -1 || bRank !== -1) {
|
|
964
|
-
if (aRank === -1) return 1;
|
|
965
|
-
if (bRank === -1) return -1;
|
|
966
|
-
return aRank - bRank;
|
|
967
|
-
}
|
|
968
|
-
return 0;
|
|
969
|
-
})()
|
|
970
|
-
) ||
|
|
971
972
|
Number(b.stable) - Number(a.stable) ||
|
|
972
973
|
b.tagScore - a.tagScore ||
|
|
973
974
|
b.apiLevel - a.apiLevel ||
|
|
@@ -1080,6 +1081,18 @@ function systemImagePackageToRelativeDir(packageName) {
|
|
|
1080
1081
|
return `${parts.join('/')}/`;
|
|
1081
1082
|
}
|
|
1082
1083
|
|
|
1084
|
+
function isValidInstalledSystemImage(packageName) {
|
|
1085
|
+
const relativeDir = systemImagePackageToRelativeDir(packageName);
|
|
1086
|
+
if (!relativeDir) return false;
|
|
1087
|
+
const root = path.join(activeAndroidSdkRoot(), relativeDir);
|
|
1088
|
+
const required = [
|
|
1089
|
+
path.join(root, 'package.xml'),
|
|
1090
|
+
path.join(root, 'system.img'),
|
|
1091
|
+
path.join(root, 'userdata.img'),
|
|
1092
|
+
];
|
|
1093
|
+
return required.every((filePath) => fs.existsSync(filePath));
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1083
1096
|
function systemImagePackageToAbi(packageName) {
|
|
1084
1097
|
const parts = String(packageName || '').split(';').filter(Boolean);
|
|
1085
1098
|
if (parts.length !== 4 || parts[0] !== 'system-images') {
|
|
@@ -1387,19 +1400,35 @@ class AndroidController {
|
|
|
1387
1400
|
chooseLatestSystemImage(available);
|
|
1388
1401
|
const selectedImage = rankSystemImagePool([preferredInstalled, preferredAvailable].filter(Boolean))[0] || preferredInstalled || preferredAvailable;
|
|
1389
1402
|
const stateApiLevel = Number(state.apiLevel || 0) || 0;
|
|
1390
|
-
|
|
1391
|
-
|
|
1403
|
+
const legacyLinuxArm64Image =
|
|
1404
|
+
process.platform === 'linux'
|
|
1405
|
+
&& process.arch === 'arm64'
|
|
1406
|
+
&& /system-images;android-30;default;arm64-v8a/i.test(String(state.systemImage || ''));
|
|
1407
|
+
const migrationTargetImage =
|
|
1408
|
+
process.platform === 'linux' && process.arch === 'arm64'
|
|
1409
|
+
? (
|
|
1410
|
+
rankSystemImagePool(
|
|
1411
|
+
[preferredAvailable, preferredInstalled]
|
|
1412
|
+
.filter(Boolean)
|
|
1413
|
+
.filter((image) => image.arch === 'arm64-v8a' && image.stable && image.apiLevel >= 33)
|
|
1414
|
+
)[0] || null
|
|
1415
|
+
)
|
|
1416
|
+
: null;
|
|
1417
|
+
const effectiveSelectedImage = migrationTargetImage || selectedImage;
|
|
1418
|
+
const selectedImageInvalid = effectiveSelectedImage?.packageName && !isValidInstalledSystemImage(effectiveSelectedImage.packageName);
|
|
1419
|
+
|
|
1420
|
+
if (!shouldForceSdkRefresh() && !legacyLinuxArm64Image && !selectedImageInvalid && sharedAndroidSdkReady() && effectiveSelectedImage) {
|
|
1392
1421
|
const stateAligned =
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1422
|
+
effectiveSelectedImage.packageName === state.systemImage &&
|
|
1423
|
+
effectiveSelectedImage.apiLevel === stateApiLevel &&
|
|
1424
|
+
effectiveSelectedImage.arch === state.systemImageArch &&
|
|
1396
1425
|
state.avdName === this.avdName;
|
|
1397
1426
|
|
|
1398
1427
|
if (stateAligned) {
|
|
1399
1428
|
return;
|
|
1400
1429
|
}
|
|
1401
1430
|
|
|
1402
|
-
if (
|
|
1431
|
+
if (effectiveSelectedImage === preferredInstalled && effectiveSelectedImage.packageName) {
|
|
1403
1432
|
const changeSummary = describeAutoFixChanges(
|
|
1404
1433
|
{
|
|
1405
1434
|
avdName: state.avdName || null,
|
|
@@ -1409,9 +1438,9 @@ class AndroidController {
|
|
|
1409
1438
|
},
|
|
1410
1439
|
{
|
|
1411
1440
|
avdName: this.avdName,
|
|
1412
|
-
systemImage:
|
|
1413
|
-
apiLevel:
|
|
1414
|
-
systemImageArch:
|
|
1441
|
+
systemImage: effectiveSelectedImage.packageName,
|
|
1442
|
+
apiLevel: effectiveSelectedImage.apiLevel,
|
|
1443
|
+
systemImageArch: effectiveSelectedImage.arch,
|
|
1415
1444
|
},
|
|
1416
1445
|
['avdName', 'systemImage', 'apiLevel', 'systemImageArch']
|
|
1417
1446
|
);
|
|
@@ -1423,9 +1452,9 @@ class AndroidController {
|
|
|
1423
1452
|
avdName: this.avdName,
|
|
1424
1453
|
serial: null,
|
|
1425
1454
|
emulatorPid: null,
|
|
1426
|
-
systemImage:
|
|
1427
|
-
apiLevel:
|
|
1428
|
-
systemImageArch:
|
|
1455
|
+
systemImage: effectiveSelectedImage.packageName,
|
|
1456
|
+
apiLevel: effectiveSelectedImage.apiLevel,
|
|
1457
|
+
systemImageArch: effectiveSelectedImage.arch,
|
|
1429
1458
|
});
|
|
1430
1459
|
return;
|
|
1431
1460
|
}
|
|
@@ -1451,12 +1480,12 @@ class AndroidController {
|
|
|
1451
1480
|
const runtimeNeedsRefresh =
|
|
1452
1481
|
state.systemImageArch !== desiredArch ||
|
|
1453
1482
|
!installedEmulatorMatchesHost();
|
|
1454
|
-
if (!shouldForceSdkRefresh()) {
|
|
1455
|
-
if (!runtimeNeedsRefresh &&
|
|
1483
|
+
if (!shouldForceSdkRefresh() && !legacyLinuxArm64Image && !selectedImageInvalid) {
|
|
1484
|
+
if (!runtimeNeedsRefresh && effectiveSelectedImage && effectiveSelectedImage === preferredInstalled) {
|
|
1456
1485
|
const stateNeedsRefresh =
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1486
|
+
effectiveSelectedImage.packageName !== state.systemImage ||
|
|
1487
|
+
effectiveSelectedImage.apiLevel !== stateApiLevel ||
|
|
1488
|
+
effectiveSelectedImage.arch !== state.systemImageArch ||
|
|
1460
1489
|
state.avdName !== this.avdName;
|
|
1461
1490
|
if (stateNeedsRefresh) {
|
|
1462
1491
|
const changeSummary = describeAutoFixChanges(
|
|
@@ -1468,9 +1497,9 @@ class AndroidController {
|
|
|
1468
1497
|
},
|
|
1469
1498
|
{
|
|
1470
1499
|
avdName: this.avdName,
|
|
1471
|
-
systemImage:
|
|
1472
|
-
apiLevel:
|
|
1473
|
-
systemImageArch:
|
|
1500
|
+
systemImage: effectiveSelectedImage.packageName,
|
|
1501
|
+
apiLevel: effectiveSelectedImage.apiLevel,
|
|
1502
|
+
systemImageArch: effectiveSelectedImage.arch,
|
|
1474
1503
|
},
|
|
1475
1504
|
['avdName', 'systemImage', 'apiLevel', 'systemImageArch']
|
|
1476
1505
|
);
|
|
@@ -1482,9 +1511,9 @@ class AndroidController {
|
|
|
1482
1511
|
avdName: this.avdName,
|
|
1483
1512
|
serial: null,
|
|
1484
1513
|
emulatorPid: null,
|
|
1485
|
-
systemImage:
|
|
1486
|
-
apiLevel:
|
|
1487
|
-
systemImageArch:
|
|
1514
|
+
systemImage: effectiveSelectedImage.packageName,
|
|
1515
|
+
apiLevel: effectiveSelectedImage.apiLevel,
|
|
1516
|
+
systemImageArch: effectiveSelectedImage.arch,
|
|
1488
1517
|
});
|
|
1489
1518
|
}
|
|
1490
1519
|
return;
|
|
@@ -1493,8 +1522,8 @@ class AndroidController {
|
|
|
1493
1522
|
!runtimeNeedsRefresh &&
|
|
1494
1523
|
state.bootstrapped === true &&
|
|
1495
1524
|
state.systemImage &&
|
|
1496
|
-
|
|
1497
|
-
|
|
1525
|
+
effectiveSelectedImage &&
|
|
1526
|
+
effectiveSelectedImage.packageName === state.systemImage
|
|
1498
1527
|
) {
|
|
1499
1528
|
return;
|
|
1500
1529
|
}
|
|
@@ -1506,18 +1535,19 @@ class AndroidController {
|
|
|
1506
1535
|
await installPlatformToolsArchive(metadata);
|
|
1507
1536
|
}
|
|
1508
1537
|
await installEmulatorArchive(metadata);
|
|
1509
|
-
if (!
|
|
1538
|
+
if (!effectiveSelectedImage) {
|
|
1510
1539
|
throw new Error(formatSystemImageError(available));
|
|
1511
1540
|
}
|
|
1512
|
-
if (
|
|
1513
|
-
await installSystemImageArchive(systemImageMetadata,
|
|
1541
|
+
if (effectiveSelectedImage?.packageName) {
|
|
1542
|
+
await installSystemImageArchive(systemImageMetadata, effectiveSelectedImage.packageName);
|
|
1514
1543
|
}
|
|
1515
1544
|
this.#appendState({
|
|
1516
1545
|
bootstrapped: true,
|
|
1517
1546
|
avdName: this.avdName,
|
|
1518
|
-
systemImage:
|
|
1519
|
-
apiLevel:
|
|
1520
|
-
systemImageArch:
|
|
1547
|
+
systemImage: effectiveSelectedImage.packageName,
|
|
1548
|
+
apiLevel: effectiveSelectedImage.apiLevel,
|
|
1549
|
+
systemImageArch: effectiveSelectedImage.arch,
|
|
1550
|
+
avdSystemImage: null,
|
|
1521
1551
|
});
|
|
1522
1552
|
}
|
|
1523
1553
|
|
|
@@ -1585,8 +1615,22 @@ class AndroidController {
|
|
|
1585
1615
|
await this.ensureBootstrapped();
|
|
1586
1616
|
|
|
1587
1617
|
const state = this.#readState();
|
|
1588
|
-
|
|
1618
|
+
let pkg = state.systemImage;
|
|
1589
1619
|
if (!pkg) throw new Error('Android system image not installed');
|
|
1620
|
+
if (process.platform === 'linux' && process.arch === 'arm64') {
|
|
1621
|
+
const installedCandidates = parseInstalledSystemImages();
|
|
1622
|
+
const migratedImage = chooseStableRuntimeSystemImage(installedCandidates, pkg);
|
|
1623
|
+
if (migratedImage) {
|
|
1624
|
+
pkg = migratedImage.packageName;
|
|
1625
|
+
this.#appendState({
|
|
1626
|
+
systemImage: pkg,
|
|
1627
|
+
apiLevel: migratedImage.apiLevel,
|
|
1628
|
+
systemImageArch: migratedImage.arch,
|
|
1629
|
+
avdSystemImage: null,
|
|
1630
|
+
lastLogLine: `Migrated Android runtime image to ${pkg} for stability.`,
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1590
1634
|
const avdDir = path.join(AVD_HOME, `${this.avdName}.avd`);
|
|
1591
1635
|
const configPath = path.join(avdDir, 'config.ini');
|
|
1592
1636
|
const avdExists = fs.existsSync(configPath);
|
|
@@ -1723,6 +1767,8 @@ class AndroidController {
|
|
|
1723
1767
|
`sdcard.size=${DEFAULT_SDCARD_SIZE_BYTES}`,
|
|
1724
1768
|
'runtime.network.latency=none',
|
|
1725
1769
|
'runtime.network.speed=full',
|
|
1770
|
+
'fastboot.forceColdBoot=yes',
|
|
1771
|
+
'fastboot.forceFastBoot=no',
|
|
1726
1772
|
'vm.heapSize=256',
|
|
1727
1773
|
`tag.display=${tagDisplay}`,
|
|
1728
1774
|
`tag.id=${tagId}`,
|
|
@@ -1746,6 +1792,8 @@ class AndroidController {
|
|
|
1746
1792
|
content = updateIniValue(content, 'sdcard.size', DEFAULT_SDCARD_SIZE_BYTES);
|
|
1747
1793
|
content = updateIniValue(content, 'hw.ramSize', DEFAULT_RAM_SIZE_MB);
|
|
1748
1794
|
content = updateIniValue(content, 'hw.gpu.mode', emulatorGpuMode());
|
|
1795
|
+
content = updateIniValue(content, 'fastboot.forceColdBoot', 'yes');
|
|
1796
|
+
content = updateIniValue(content, 'fastboot.forceFastBoot', 'no');
|
|
1749
1797
|
content = updateIniValue(content, 'PlayStore.enabled', String(this.#readState()?.systemImage || '').includes('playstore'));
|
|
1750
1798
|
fs.writeFileSync(configPath, content);
|
|
1751
1799
|
}
|
|
@@ -1783,6 +1831,21 @@ class AndroidController {
|
|
|
1783
1831
|
} catch {}
|
|
1784
1832
|
}
|
|
1785
1833
|
|
|
1834
|
+
async #forceRecreateAvdForRecovery() {
|
|
1835
|
+
const avdDir = path.join(AVD_HOME, `${this.avdName}.avd`);
|
|
1836
|
+
await this.stopEmulator().catch(() => {});
|
|
1837
|
+
fs.rmSync(avdDir, { recursive: true, force: true });
|
|
1838
|
+
fs.rmSync(path.join(AVD_HOME, `${this.avdName}.ini`), { force: true });
|
|
1839
|
+
this.#appendState({
|
|
1840
|
+
avdSystemImage: null,
|
|
1841
|
+
serial: null,
|
|
1842
|
+
emulatorPid: null,
|
|
1843
|
+
lastLogLine: 'Recreating AVD after failed framework boot.',
|
|
1844
|
+
});
|
|
1845
|
+
await this.ensureAvd();
|
|
1846
|
+
this.#cleanupAvdTransientState();
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1786
1849
|
async listDevices(options = {}) {
|
|
1787
1850
|
if (options.ensureBootstrapped !== false) {
|
|
1788
1851
|
await this.ensureBootstrapped();
|
|
@@ -1921,6 +1984,11 @@ class AndroidController {
|
|
|
1921
1984
|
this.#cleanupAvdTransientState();
|
|
1922
1985
|
return this.#startEmulatorBlocking({ ...options, _recoveredOnce: true });
|
|
1923
1986
|
}
|
|
1987
|
+
if (!options._recreatedAvdOnce && isRecoverableEmulatorStartError(lastLine)) {
|
|
1988
|
+
console.warn(`[Android] Emulator recovery escalation: recreating AVD and retrying once: ${lastLine}`);
|
|
1989
|
+
await this.#forceRecreateAvdForRecovery();
|
|
1990
|
+
return this.#startEmulatorBlocking({ ...options, _recoveredOnce: true, _recreatedAvdOnce: true });
|
|
1991
|
+
}
|
|
1924
1992
|
this.markBootstrapFailure(lastLine);
|
|
1925
1993
|
throw new Error(lastLine);
|
|
1926
1994
|
}
|
|
@@ -2059,6 +2127,7 @@ class AndroidController {
|
|
|
2059
2127
|
let reconnectCounter = 0;
|
|
2060
2128
|
let missingPidSince = null;
|
|
2061
2129
|
let firstOnlineAt = null;
|
|
2130
|
+
let offlineSince = null;
|
|
2062
2131
|
|
|
2063
2132
|
while (Date.now() < deadline) {
|
|
2064
2133
|
const serial = await this.getPrimarySerial();
|
|
@@ -2083,22 +2152,44 @@ class AndroidController {
|
|
|
2083
2152
|
`${quoteShell(adbBinary())} -s ${quoteShell(serial)} shell echo ready`,
|
|
2084
2153
|
{ timeout: 10000 },
|
|
2085
2154
|
);
|
|
2155
|
+
const packageServiceProbe = await this.#runAllowFailure(
|
|
2156
|
+
`${quoteShell(adbBinary())} -s ${quoteShell(serial)} shell service check package`,
|
|
2157
|
+
{ timeout: 10000 },
|
|
2158
|
+
);
|
|
2159
|
+
const pmProbe = await this.#runAllowFailure(
|
|
2160
|
+
`${quoteShell(adbBinary())} -s ${quoteShell(serial)} shell pm path android`,
|
|
2161
|
+
{ timeout: 10000 },
|
|
2162
|
+
);
|
|
2086
2163
|
|
|
2087
2164
|
const bootValue = String(bootCompleted.stdout || '').trim();
|
|
2088
2165
|
const devBootValue = String(devBootComplete.stdout || '').trim();
|
|
2089
2166
|
const bootAnimValue = String(bootAnim.stdout || '').trim().toLowerCase();
|
|
2090
2167
|
const shellReady = String(shellProbe.stdout || '').trim() === 'ready';
|
|
2168
|
+
const packageServiceReady = /found/i.test(String(packageServiceProbe.stdout || ''));
|
|
2169
|
+
const packageManagerReady = /^package:/m.test(String(pmProbe.stdout || '').trim());
|
|
2091
2170
|
if (
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2171
|
+
packageServiceReady
|
|
2172
|
+
&& packageManagerReady
|
|
2173
|
+
&& (
|
|
2174
|
+
bootValue === '1'
|
|
2175
|
+
|| devBootValue === '1'
|
|
2176
|
+
|| (shellReady && (bootAnimValue === 'stopped' || bootAnimValue === ''))
|
|
2177
|
+
)
|
|
2095
2178
|
) {
|
|
2096
2179
|
return serial;
|
|
2097
2180
|
}
|
|
2098
|
-
if (
|
|
2099
|
-
|
|
2181
|
+
if (
|
|
2182
|
+
firstOnlineAt
|
|
2183
|
+
&& Date.now() - firstOnlineAt > 120000
|
|
2184
|
+
&& (
|
|
2185
|
+
!packageServiceReady
|
|
2186
|
+
|| !packageManagerReady
|
|
2187
|
+
)
|
|
2188
|
+
) {
|
|
2189
|
+
throw new Error('Android framework did not become ready (package manager service did not become ready).');
|
|
2100
2190
|
}
|
|
2101
2191
|
missingPidSince = null;
|
|
2192
|
+
offlineSince = null;
|
|
2102
2193
|
} else {
|
|
2103
2194
|
firstOnlineAt = null;
|
|
2104
2195
|
const state = this.#readState();
|
|
@@ -2115,6 +2206,23 @@ class AndroidController {
|
|
|
2115
2206
|
}
|
|
2116
2207
|
} else {
|
|
2117
2208
|
missingPidSince = null;
|
|
2209
|
+
const devices = await this.listDevices({ ensureBootstrapped: false }).catch(() => []);
|
|
2210
|
+
const hasOfflineEmulator = devices.some((device) => device.emulator && device.status === 'offline');
|
|
2211
|
+
if (hasOfflineEmulator) {
|
|
2212
|
+
if (!offlineSince) {
|
|
2213
|
+
offlineSince = Date.now();
|
|
2214
|
+
}
|
|
2215
|
+
if (Date.now() - offlineSince > 30000) {
|
|
2216
|
+
await this.#runAllowFailure(`${quoteShell(adbBinary())} kill-server`, { timeout: 10000 });
|
|
2217
|
+
await sleep(600);
|
|
2218
|
+
await this.#runAllowFailure(`${quoteShell(adbBinary())} start-server`, { timeout: 15000 });
|
|
2219
|
+
await this.#runAllowFailure(`${quoteShell(adbBinary())} reconnect`, { timeout: 15000 });
|
|
2220
|
+
await this.#runAllowFailure(`${quoteShell(adbBinary())} wait-for-device`, { timeout: 30000 });
|
|
2221
|
+
offlineSince = Date.now();
|
|
2222
|
+
}
|
|
2223
|
+
} else {
|
|
2224
|
+
offlineSince = null;
|
|
2225
|
+
}
|
|
2118
2226
|
}
|
|
2119
2227
|
}
|
|
2120
2228
|
reconnectCounter += 1;
|
|
@@ -2184,8 +2292,17 @@ class AndroidController {
|
|
|
2184
2292
|
return this.#run(`${quoteShell(adbBinary())} -s ${quoteShell(serial)} ${command}`, options);
|
|
2185
2293
|
}
|
|
2186
2294
|
|
|
2295
|
+
async #prepareScreenForCapture(serial) {
|
|
2296
|
+
await this.#adb(serial, 'shell input keyevent 224', { timeout: 10000 }).catch(() => {});
|
|
2297
|
+
await this.#adb(serial, 'shell wm dismiss-keyguard', { timeout: 10000 }).catch(() => {});
|
|
2298
|
+
await this.#adb(serial, 'shell input keyevent 82', { timeout: 10000 }).catch(() => {});
|
|
2299
|
+
await this.#adb(serial, 'shell input keyevent 3', { timeout: 10000 }).catch(() => {});
|
|
2300
|
+
await sleep(350);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2187
2303
|
async screenshot(options = {}) {
|
|
2188
2304
|
const serial = options.serial || await this.ensureDevice();
|
|
2305
|
+
await this.#prepareScreenForCapture(serial).catch(() => {});
|
|
2189
2306
|
let artifactRecord = null;
|
|
2190
2307
|
let filename = `android_${Date.now()}.png`;
|
|
2191
2308
|
let fullPath = path.join(SCREENSHOTS_DIR, filename);
|
|
@@ -2203,7 +2320,32 @@ class AndroidController {
|
|
|
2203
2320
|
fullPath = artifactRecord.storagePath;
|
|
2204
2321
|
filename = path.basename(fullPath);
|
|
2205
2322
|
}
|
|
2206
|
-
|
|
2323
|
+
let captured = false;
|
|
2324
|
+
const localTmp = path.join(TMP_DIR, `shot-${Date.now()}-${Math.random().toString(16).slice(2)}.png`);
|
|
2325
|
+
const remoteTmp = `/sdcard/neoagent-shot-${Date.now()}.png`;
|
|
2326
|
+
for (let attempt = 0; attempt < 3 && !captured; attempt += 1) {
|
|
2327
|
+
try {
|
|
2328
|
+
if (attempt === 0) {
|
|
2329
|
+
await this.#adb(serial, `exec-out screencap -p > ${quoteShell(fullPath)}`, { timeout: 30000 });
|
|
2330
|
+
} else {
|
|
2331
|
+
await this.#adb(serial, `shell screencap -p ${quoteShell(remoteTmp)}`, { timeout: 30000 });
|
|
2332
|
+
await this.#adb(serial, `pull ${quoteShell(remoteTmp)} ${quoteShell(localTmp)}`, { timeout: 30000 });
|
|
2333
|
+
if (fs.existsSync(localTmp)) {
|
|
2334
|
+
fs.copyFileSync(localTmp, fullPath);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
const data = fs.readFileSync(fullPath);
|
|
2338
|
+
captured = isLikelyPng(data);
|
|
2339
|
+
} catch {}
|
|
2340
|
+
if (!captured) {
|
|
2341
|
+
await sleep(500);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
fs.rmSync(localTmp, { force: true });
|
|
2345
|
+
await this.#adb(serial, `shell rm -f ${quoteShell(remoteTmp)}`, { timeout: 10000 }).catch(() => {});
|
|
2346
|
+
if (!captured) {
|
|
2347
|
+
throw new Error('Failed to capture a valid Android screenshot.');
|
|
2348
|
+
}
|
|
2207
2349
|
if (artifactRecord) {
|
|
2208
2350
|
this.artifactStore.finalizeFile(artifactRecord.artifactId, fullPath);
|
|
2209
2351
|
}
|
|
@@ -2561,9 +2703,36 @@ class AndroidController {
|
|
|
2561
2703
|
}
|
|
2562
2704
|
|
|
2563
2705
|
async listApps(args = {}) {
|
|
2564
|
-
|
|
2706
|
+
let serial = null;
|
|
2707
|
+
try {
|
|
2708
|
+
serial = await this.ensureDevice();
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
return {
|
|
2711
|
+
success: false,
|
|
2712
|
+
serial: null,
|
|
2713
|
+
count: 0,
|
|
2714
|
+
packages: [],
|
|
2715
|
+
error: String(error?.message || 'Android device is not ready.'),
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2565
2718
|
const cmd = args.includeSystem === true ? 'shell pm list packages' : 'shell pm list packages -3';
|
|
2566
|
-
|
|
2719
|
+
let out = '';
|
|
2720
|
+
try {
|
|
2721
|
+
out = await this.#adb(serial, cmd, { timeout: 30000 });
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
await sleep(1000);
|
|
2724
|
+
try {
|
|
2725
|
+
out = await this.#adb(serial, cmd, { timeout: 30000 });
|
|
2726
|
+
} catch (retryError) {
|
|
2727
|
+
return {
|
|
2728
|
+
success: false,
|
|
2729
|
+
serial,
|
|
2730
|
+
count: 0,
|
|
2731
|
+
packages: [],
|
|
2732
|
+
error: String(retryError?.message || error?.message || 'Failed to list Android apps.'),
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2567
2736
|
const packages = out
|
|
2568
2737
|
.split('\n')
|
|
2569
2738
|
.map((line) => line.trim())
|
|
@@ -2642,6 +2811,8 @@ class AndroidController {
|
|
|
2642
2811
|
|
|
2643
2812
|
async getStatus() {
|
|
2644
2813
|
const state = this.#readState();
|
|
2814
|
+
const bootstrapWorkerPid = Number(state.bootstrapWorkerPid || 0) || null;
|
|
2815
|
+
const bootstrapWorkerAlive = isProcessAlive(bootstrapWorkerPid);
|
|
2645
2816
|
const devices = isExecutable(adbBinary())
|
|
2646
2817
|
? await this.listDevices({ ensureBootstrapped: false }).catch(() => [])
|
|
2647
2818
|
: [];
|
|
@@ -2674,8 +2845,8 @@ class AndroidController {
|
|
|
2674
2845
|
}
|
|
2675
2846
|
return {
|
|
2676
2847
|
bootstrapped: state.bootstrapped === true,
|
|
2677
|
-
starting: state.starting === true || this.startPromise != null,
|
|
2678
|
-
startupPhase: state.startupPhase || null,
|
|
2848
|
+
starting: state.starting === true || this.startPromise != null || bootstrapWorkerAlive,
|
|
2849
|
+
startupPhase: state.startupPhase || (bootstrapWorkerAlive ? 'Preparing Android runtime' : null),
|
|
2679
2850
|
startRequestedAt: state.startRequestedAt || null,
|
|
2680
2851
|
lastStartError: state.lastStartError || null,
|
|
2681
2852
|
sdkRoot: activeAndroidSdkRoot(),
|
|
@@ -2686,7 +2857,7 @@ class AndroidController {
|
|
|
2686
2857
|
serial: state.serial,
|
|
2687
2858
|
serialOwnedByCurrentUser,
|
|
2688
2859
|
emulatorPid: state.emulatorPid,
|
|
2689
|
-
bootstrapWorkerPid
|
|
2860
|
+
bootstrapWorkerPid,
|
|
2690
2861
|
systemImage: state.systemImage || null,
|
|
2691
2862
|
systemImageArch: state.systemImageArch || null,
|
|
2692
2863
|
preferredSystemImageArchs: systemImageArchCandidates(),
|
|
@@ -193,6 +193,14 @@ class VmBrowserProvider {
|
|
|
193
193
|
} catch {}
|
|
194
194
|
}
|
|
195
195
|
if (!file?.content) {
|
|
196
|
+
if (typeof result.screenshotPath === 'string' && result.screenshotPath.startsWith('/screenshots/')) {
|
|
197
|
+
return {
|
|
198
|
+
...result,
|
|
199
|
+
screenshotPath: null,
|
|
200
|
+
artifactId: result.artifactId || null,
|
|
201
|
+
fullPath: result.fullPath || null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
196
204
|
return result;
|
|
197
205
|
}
|
|
198
206
|
|