appium-android-driver 14.2.0 → 14.2.2
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [14.2.2](https://github.com/appium/appium-android-driver/compare/v14.2.1...v14.2.2) (2026-09-26)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* preserve container file transfers and newline paths ([#1101](https://github.com/appium/appium-android-driver/issues/1101)) ([3685fe6](https://github.com/appium/appium-android-driver/commit/3685fe62d2d36c69cbac521e4645a0c10a0a948f))
|
|
6
|
+
* preserve file operations on older Android ([#1102](https://github.com/appium/appium-android-driver/issues/1102)) ([43c8bb2](https://github.com/appium/appium-android-driver/commit/43c8bb2e575cde5258c8de737f30c3b555b17d01))
|
|
7
|
+
|
|
8
|
+
## [14.2.1](https://github.com/appium/appium-android-driver/compare/v14.2.0...v14.2.1) (2026-09-26)
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
* preserve literal paths in Android file actions ([#1100](https://github.com/appium/appium-android-driver/issues/1100)) ([de47b29](https://github.com/appium/appium-android-driver/commit/de47b293d9b978cfbaf6e7268909a06faab85d08))
|
|
13
|
+
|
|
1
14
|
## [14.2.0](https://github.com/appium/appium-android-driver/compare/v14.1.0...v14.2.0) (2026-09-10)
|
|
2
15
|
|
|
3
16
|
### Features
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-actions.d.ts","sourceRoot":"","sources":["../../../lib/commands/file-actions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"file-actions.d.ts","sourceRoot":"","sources":["../../../lib/commands/file-actions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAMhD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+CvF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DpH;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYzF;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOhG"}
|
|
@@ -2,8 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { fs, util, zip, tempDir } from '@appium/support';
|
|
3
3
|
import { errors } from 'appium/driver.js';
|
|
4
4
|
const CONTAINER_PATH_MARKER = '@';
|
|
5
|
-
|
|
6
|
-
const CONTAINER_PATH_PATTERN = new RegExp(`^${CONTAINER_PATH_MARKER}([^/]+)/(.+)`);
|
|
5
|
+
const CONTAINER_PATH_PATTERN = new RegExp(`^${CONTAINER_PATH_MARKER}([^/]+)/(.+)`, 's');
|
|
7
6
|
const ANDROID_MEDIA_RESCAN_INTENT = 'android.intent.action.MEDIA_SCANNER_SCAN_FILE';
|
|
8
7
|
/**
|
|
9
8
|
* Pulls a file from the remote device.
|
|
@@ -25,35 +24,43 @@ export async function pullFile(remotePath) {
|
|
|
25
24
|
throw new errors.InvalidArgumentError(`It is expected that remote path points to a file and not to a folder. '${remotePath}' is given instead`);
|
|
26
25
|
}
|
|
27
26
|
let tmpDestination = null;
|
|
28
|
-
if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
|
|
29
|
-
const [packageId, pathInContainer] = parseContainerPath(remotePath);
|
|
30
|
-
this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`);
|
|
31
|
-
tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
|
|
32
|
-
try {
|
|
33
|
-
await this.adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
|
|
34
|
-
await this.adb.shell([
|
|
35
|
-
'run-as',
|
|
36
|
-
packageId,
|
|
37
|
-
`cp -f '${escapePath(pathInContainer)}' '${escapePath(tmpDestination)}'`,
|
|
38
|
-
]);
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
41
|
-
throw this.log.errorWithException(`Cannot access the container of '${packageId}' application. ` +
|
|
42
|
-
`Is the application installed and has 'debuggable' build option set to true? ` +
|
|
43
|
-
`Original error: ${e.message}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
27
|
const localFile = await tempDir.path({ prefix: 'appium', suffix: '.tmp' });
|
|
47
28
|
try {
|
|
29
|
+
if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
|
|
30
|
+
const [packageId, pathInContainer] = parseContainerPath(remotePath);
|
|
31
|
+
this.log.debug(`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`);
|
|
32
|
+
try {
|
|
33
|
+
tmpDestination = await this.adb.shell(['mktemp', '/data/local/tmp/appium-pull-XXXXXX']);
|
|
34
|
+
// The shell user opens the destination; run-as only needs to read the source.
|
|
35
|
+
// App users cannot create files in /data/local/tmp on non-rooted devices.
|
|
36
|
+
await this.adb.shell([
|
|
37
|
+
'run-as',
|
|
38
|
+
util.quote(packageId),
|
|
39
|
+
'cat',
|
|
40
|
+
util.quote(pathInContainer),
|
|
41
|
+
'>',
|
|
42
|
+
util.quote(tmpDestination),
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
throw this.log.errorWithException(`Cannot access the container of '${packageId}' application. ` +
|
|
47
|
+
`Is the application installed and has 'debuggable' build option set to true? ` +
|
|
48
|
+
`Original error: ${e.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
48
51
|
await this.adb.pull(tmpDestination || remotePath, localFile);
|
|
49
52
|
return (await util.toInMemoryBase64(localFile)).toString();
|
|
50
53
|
}
|
|
51
54
|
finally {
|
|
52
|
-
|
|
53
|
-
await fs.
|
|
55
|
+
try {
|
|
56
|
+
if (await fs.exists(localFile)) {
|
|
57
|
+
await fs.unlink(localFile);
|
|
58
|
+
}
|
|
54
59
|
}
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
finally {
|
|
61
|
+
if (tmpDestination) {
|
|
62
|
+
await this.adb.shell(['rm', '-f', util.quote(tmpDestination)]);
|
|
63
|
+
}
|
|
57
64
|
}
|
|
58
65
|
}
|
|
59
66
|
}
|
|
@@ -98,13 +105,17 @@ export async function pushFile(remotePath, base64Data) {
|
|
|
98
105
|
`Will put the data into '${pathInContainer}'`);
|
|
99
106
|
tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
|
|
100
107
|
try {
|
|
101
|
-
await this.adb.shell([
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
await this.adb.shell([
|
|
109
|
+
'run-as',
|
|
110
|
+
util.quote(packageId),
|
|
111
|
+
`mkdir -p '${escapePath(path.posix.dirname(pathInContainer))}'`,
|
|
112
|
+
]);
|
|
113
|
+
await this.adb.shell(['run-as', util.quote(packageId), `touch '${escapePath(pathInContainer)}'`]);
|
|
114
|
+
await this.adb.shell(['run-as', util.quote(packageId), `chmod 777 '${escapePath(pathInContainer)}'`]);
|
|
104
115
|
await this.adb.push(localFile, tmpDestination);
|
|
105
116
|
await this.adb.shell([
|
|
106
117
|
'run-as',
|
|
107
|
-
packageId,
|
|
118
|
+
util.quote(packageId),
|
|
108
119
|
`cp -f '${escapePath(tmpDestination)}' '${escapePath(pathInContainer)}'`,
|
|
109
120
|
]);
|
|
110
121
|
}
|
|
@@ -127,7 +138,7 @@ export async function pushFile(remotePath, base64Data) {
|
|
|
127
138
|
await fs.unlink(localFile);
|
|
128
139
|
}
|
|
129
140
|
if (tmpDestination) {
|
|
130
|
-
await this.adb.shell(['rm', '-f', tmpDestination]);
|
|
141
|
+
await this.adb.shell(['rm', '-f', util.quote(tmpDestination)]);
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
}
|
|
@@ -184,7 +195,7 @@ async function deleteFileOrFolder(adb, remotePath) {
|
|
|
184
195
|
}
|
|
185
196
|
if (pkgId) {
|
|
186
197
|
try {
|
|
187
|
-
await adb.shell(['run-as', pkgId, 'ls']);
|
|
198
|
+
await adb.shell(['run-as', util.quote(pkgId), 'ls']);
|
|
188
199
|
}
|
|
189
200
|
catch (e) {
|
|
190
201
|
throw this.log.errorWithException(`Cannot access the container of '${pkgId}' application. ` +
|
|
@@ -204,10 +215,10 @@ async function deleteFileOrFolder(adb, remotePath) {
|
|
|
204
215
|
throw this.log.errorWithException(`The item at '${dstPath}' is not a folder`);
|
|
205
216
|
}
|
|
206
217
|
if (pkgId) {
|
|
207
|
-
await adb.shell(['run-as', pkgId, `rm -f${expectsFile ? '' : 'r'} '${escapePath(dstPath)}'`]);
|
|
218
|
+
await adb.shell(['run-as', util.quote(pkgId), `rm -f${expectsFile ? '' : 'r'} '${escapePath(dstPath)}'`]);
|
|
208
219
|
}
|
|
209
220
|
else {
|
|
210
|
-
await adb.shell(['rm', `-f${expectsFile ? '' : 'r'}`, dstPath]);
|
|
221
|
+
await adb.shell(['rm', `-f${expectsFile ? '' : 'r'}`, util.quote(dstPath)]);
|
|
211
222
|
}
|
|
212
223
|
if (await isPresent(dstPath, pkgId)) {
|
|
213
224
|
throw this.log.errorWithException(`The item at '${dstPath}' still exists after being deleted. Is it writable?`);
|
|
@@ -243,7 +254,14 @@ async function scanMedia(remotePath) {
|
|
|
243
254
|
await this.settingsApp.scanMedia(remotePath);
|
|
244
255
|
}
|
|
245
256
|
else {
|
|
246
|
-
await this.adb.shell([
|
|
257
|
+
await this.adb.shell([
|
|
258
|
+
'am',
|
|
259
|
+
'broadcast',
|
|
260
|
+
'-a',
|
|
261
|
+
ANDROID_MEDIA_RESCAN_INTENT,
|
|
262
|
+
'-d',
|
|
263
|
+
util.quote(`file://${remotePath}`),
|
|
264
|
+
]);
|
|
247
265
|
}
|
|
248
266
|
}
|
|
249
267
|
catch (e) {
|
|
@@ -257,7 +275,7 @@ async function scanMedia(remotePath) {
|
|
|
257
275
|
* so they are safe to be passed as arguments of shell commands
|
|
258
276
|
*/
|
|
259
277
|
function escapePath(p) {
|
|
260
|
-
return p.replace(/'/g,
|
|
278
|
+
return p.replace(/'/g, `'"'"'`);
|
|
261
279
|
}
|
|
262
280
|
/**
|
|
263
281
|
* Factory providing filesystem test functions using ADB
|
|
@@ -266,7 +284,9 @@ function createFSTests(adb) {
|
|
|
266
284
|
const performRemoteFsCheck = async (p, op, runAs) => {
|
|
267
285
|
const passFlag = '__PASS__';
|
|
268
286
|
const checkCmd = `[ -${op} '${escapePath(p)}' ] && echo ${passFlag}`;
|
|
269
|
-
|
|
287
|
+
// Older Android images provide [ as a shell builtin, not an executable
|
|
288
|
+
// that run-as can invoke directly.
|
|
289
|
+
const fullCmd = runAs ? `run-as ${util.quote(runAs)} sh -c '${escapePath(checkCmd)}'` : checkCmd;
|
|
270
290
|
try {
|
|
271
291
|
return (await adb.shell([fullCmd])).includes(passFlag);
|
|
272
292
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-actions.js","sourceRoot":"","sources":["../../../lib/commands/file-actions.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAC,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAC,MAAM,EAAC,MAAM,kBAAkB,CAAC;AAIxC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,
|
|
1
|
+
{"version":3,"file":"file-actions.js","sourceRoot":"","sources":["../../../lib/commands/file-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,8 +7,7 @@ import {errors} from 'appium/driver.js';
|
|
|
7
7
|
import type {AndroidDriver} from '../driver.js';
|
|
8
8
|
|
|
9
9
|
const CONTAINER_PATH_MARKER = '@';
|
|
10
|
-
|
|
11
|
-
const CONTAINER_PATH_PATTERN = new RegExp(`^${CONTAINER_PATH_MARKER}([^/]+)/(.+)`);
|
|
10
|
+
const CONTAINER_PATH_PATTERN = new RegExp(`^${CONTAINER_PATH_MARKER}([^/]+)/(.+)`, 's');
|
|
12
11
|
const ANDROID_MEDIA_RESCAN_INTENT = 'android.intent.action.MEDIA_SCANNER_SCAN_FILE';
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -33,37 +32,44 @@ export async function pullFile(this: AndroidDriver, remotePath: string): Promise
|
|
|
33
32
|
);
|
|
34
33
|
}
|
|
35
34
|
let tmpDestination: string | null = null;
|
|
36
|
-
if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
|
|
37
|
-
const [packageId, pathInContainer] = parseContainerPath(remotePath);
|
|
38
|
-
this.log.debug(
|
|
39
|
-
`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`,
|
|
40
|
-
);
|
|
41
|
-
tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
|
|
42
|
-
try {
|
|
43
|
-
await this.adb.shell(['run-as', packageId, `chmod 777 '${escapePath(pathInContainer)}'`]);
|
|
44
|
-
await this.adb.shell([
|
|
45
|
-
'run-as',
|
|
46
|
-
packageId,
|
|
47
|
-
`cp -f '${escapePath(pathInContainer)}' '${escapePath(tmpDestination)}'`,
|
|
48
|
-
]);
|
|
49
|
-
} catch (e) {
|
|
50
|
-
throw this.log.errorWithException(
|
|
51
|
-
`Cannot access the container of '${packageId}' application. ` +
|
|
52
|
-
`Is the application installed and has 'debuggable' build option set to true? ` +
|
|
53
|
-
`Original error: ${(e as Error).message}`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
35
|
const localFile = await tempDir.path({prefix: 'appium', suffix: '.tmp'});
|
|
58
36
|
try {
|
|
37
|
+
if (remotePath.startsWith(CONTAINER_PATH_MARKER)) {
|
|
38
|
+
const [packageId, pathInContainer] = parseContainerPath(remotePath);
|
|
39
|
+
this.log.debug(
|
|
40
|
+
`Parsed package identifier '${packageId}' from '${remotePath}'. Will get the data from '${pathInContainer}'`,
|
|
41
|
+
);
|
|
42
|
+
try {
|
|
43
|
+
tmpDestination = await this.adb.shell(['mktemp', '/data/local/tmp/appium-pull-XXXXXX']);
|
|
44
|
+
// The shell user opens the destination; run-as only needs to read the source.
|
|
45
|
+
// App users cannot create files in /data/local/tmp on non-rooted devices.
|
|
46
|
+
await this.adb.shell([
|
|
47
|
+
'run-as',
|
|
48
|
+
util.quote(packageId),
|
|
49
|
+
'cat',
|
|
50
|
+
util.quote(pathInContainer),
|
|
51
|
+
'>',
|
|
52
|
+
util.quote(tmpDestination),
|
|
53
|
+
]);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
throw this.log.errorWithException(
|
|
56
|
+
`Cannot access the container of '${packageId}' application. ` +
|
|
57
|
+
`Is the application installed and has 'debuggable' build option set to true? ` +
|
|
58
|
+
`Original error: ${(e as Error).message}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
59
62
|
await this.adb.pull(tmpDestination || remotePath, localFile);
|
|
60
63
|
return (await util.toInMemoryBase64(localFile)).toString();
|
|
61
64
|
} finally {
|
|
62
|
-
|
|
63
|
-
await fs.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
try {
|
|
66
|
+
if (await fs.exists(localFile)) {
|
|
67
|
+
await fs.unlink(localFile);
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
if (tmpDestination) {
|
|
71
|
+
await this.adb.shell(['rm', '-f', util.quote(tmpDestination)]);
|
|
72
|
+
}
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
}
|
|
@@ -112,13 +118,17 @@ export async function pushFile(this: AndroidDriver, remotePath: string, base64Da
|
|
|
112
118
|
);
|
|
113
119
|
tmpDestination = `/data/local/tmp/${path.posix.basename(pathInContainer)}`;
|
|
114
120
|
try {
|
|
115
|
-
await this.adb.shell([
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
await this.adb.shell([
|
|
122
|
+
'run-as',
|
|
123
|
+
util.quote(packageId),
|
|
124
|
+
`mkdir -p '${escapePath(path.posix.dirname(pathInContainer))}'`,
|
|
125
|
+
]);
|
|
126
|
+
await this.adb.shell(['run-as', util.quote(packageId), `touch '${escapePath(pathInContainer)}'`]);
|
|
127
|
+
await this.adb.shell(['run-as', util.quote(packageId), `chmod 777 '${escapePath(pathInContainer)}'`]);
|
|
118
128
|
await this.adb.push(localFile, tmpDestination);
|
|
119
129
|
await this.adb.shell([
|
|
120
130
|
'run-as',
|
|
121
|
-
packageId,
|
|
131
|
+
util.quote(packageId),
|
|
122
132
|
`cp -f '${escapePath(tmpDestination)}' '${escapePath(pathInContainer)}'`,
|
|
123
133
|
]);
|
|
124
134
|
} catch (e) {
|
|
@@ -141,7 +151,7 @@ export async function pushFile(this: AndroidDriver, remotePath: string, base64Da
|
|
|
141
151
|
await fs.unlink(localFile);
|
|
142
152
|
}
|
|
143
153
|
if (tmpDestination) {
|
|
144
|
-
await this.adb.shell(['rm', '-f', tmpDestination]);
|
|
154
|
+
await this.adb.shell(['rm', '-f', util.quote(tmpDestination)]);
|
|
145
155
|
}
|
|
146
156
|
}
|
|
147
157
|
}
|
|
@@ -205,7 +215,7 @@ async function deleteFileOrFolder(this: AndroidDriver, adb: ADB, remotePath: str
|
|
|
205
215
|
|
|
206
216
|
if (pkgId) {
|
|
207
217
|
try {
|
|
208
|
-
await adb.shell(['run-as', pkgId, 'ls']);
|
|
218
|
+
await adb.shell(['run-as', util.quote(pkgId), 'ls']);
|
|
209
219
|
} catch (e) {
|
|
210
220
|
throw this.log.errorWithException(
|
|
211
221
|
`Cannot access the container of '${pkgId}' application. ` +
|
|
@@ -228,9 +238,9 @@ async function deleteFileOrFolder(this: AndroidDriver, adb: ADB, remotePath: str
|
|
|
228
238
|
}
|
|
229
239
|
|
|
230
240
|
if (pkgId) {
|
|
231
|
-
await adb.shell(['run-as', pkgId, `rm -f${expectsFile ? '' : 'r'} '${escapePath(dstPath)}'`]);
|
|
241
|
+
await adb.shell(['run-as', util.quote(pkgId), `rm -f${expectsFile ? '' : 'r'} '${escapePath(dstPath)}'`]);
|
|
232
242
|
} else {
|
|
233
|
-
await adb.shell(['rm', `-f${expectsFile ? '' : 'r'}`, dstPath]);
|
|
243
|
+
await adb.shell(['rm', `-f${expectsFile ? '' : 'r'}`, util.quote(dstPath)]);
|
|
234
244
|
}
|
|
235
245
|
if (await isPresent(dstPath, pkgId)) {
|
|
236
246
|
throw this.log.errorWithException(`The item at '${dstPath}' still exists after being deleted. Is it writable?`);
|
|
@@ -270,7 +280,14 @@ async function scanMedia(this: AndroidDriver, remotePath: string): Promise<void>
|
|
|
270
280
|
if ((await this.adb.getApiLevel()) >= 29) {
|
|
271
281
|
await this.settingsApp.scanMedia(remotePath);
|
|
272
282
|
} else {
|
|
273
|
-
await this.adb.shell([
|
|
283
|
+
await this.adb.shell([
|
|
284
|
+
'am',
|
|
285
|
+
'broadcast',
|
|
286
|
+
'-a',
|
|
287
|
+
ANDROID_MEDIA_RESCAN_INTENT,
|
|
288
|
+
'-d',
|
|
289
|
+
util.quote(`file://${remotePath}`),
|
|
290
|
+
]);
|
|
274
291
|
}
|
|
275
292
|
} catch (e) {
|
|
276
293
|
const err = e as {stderr?: string; message?: string};
|
|
@@ -284,7 +301,7 @@ async function scanMedia(this: AndroidDriver, remotePath: string): Promise<void>
|
|
|
284
301
|
* so they are safe to be passed as arguments of shell commands
|
|
285
302
|
*/
|
|
286
303
|
function escapePath(p: string): string {
|
|
287
|
-
return p.replace(/'/g,
|
|
304
|
+
return p.replace(/'/g, `'"'"'`);
|
|
288
305
|
}
|
|
289
306
|
|
|
290
307
|
/**
|
|
@@ -294,7 +311,9 @@ function createFSTests(adb: ADB) {
|
|
|
294
311
|
const performRemoteFsCheck = async (p: string, op: 'd' | 'f' | 'e', runAs?: string): Promise<boolean> => {
|
|
295
312
|
const passFlag = '__PASS__';
|
|
296
313
|
const checkCmd = `[ -${op} '${escapePath(p)}' ] && echo ${passFlag}`;
|
|
297
|
-
|
|
314
|
+
// Older Android images provide [ as a shell builtin, not an executable
|
|
315
|
+
// that run-as can invoke directly.
|
|
316
|
+
const fullCmd = runAs ? `run-as ${util.quote(runAs)} sh -c '${escapePath(checkCmd)}'` : checkCmd;
|
|
298
317
|
try {
|
|
299
318
|
return (await adb.shell([fullCmd])).includes(passFlag);
|
|
300
319
|
} catch {
|