io.appium.settings 7.0.7 → 7.0.9
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 +12 -0
- package/apks/settings_apk-debug.apk +0 -0
- package/build/lib/commands/geolocation.d.ts +12 -69
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +13 -35
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts +32 -45
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +51 -57
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/types.d.ts +38 -0
- package/build/lib/commands/types.d.ts.map +1 -0
- package/build/lib/commands/types.js +3 -0
- package/build/lib/commands/types.js.map +1 -0
- package/build/lib/commands/utf7.d.ts +17 -16
- package/build/lib/commands/utf7.d.ts.map +1 -1
- package/build/lib/commands/utf7.js +73 -91
- package/build/lib/commands/utf7.js.map +1 -1
- package/lib/commands/{geolocation.js → geolocation.ts} +25 -46
- package/lib/commands/{media-projection.js → media-projection.ts} +59 -63
- package/lib/commands/types.ts +38 -0
- package/lib/commands/{utf7.js → utf7.ts} +83 -102
- package/package.json +2 -3
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RFC 2152 UTF-7 encoding
|
|
2
|
+
* RFC 2152 UTF-7 encoding.
|
|
3
3
|
*
|
|
4
|
-
* @param
|
|
5
|
-
|
|
4
|
+
* @param mask Optional mask characters to exclude from encoding
|
|
5
|
+
*/
|
|
6
|
+
export declare const encode: (str: string, mask?: string | null) => string;
|
|
7
|
+
/**
|
|
8
|
+
* RFC 2152 UTF-7 encoding with all optionals.
|
|
9
|
+
* Encodes all non-ASCII characters, including those that would normally
|
|
10
|
+
* be represented directly in standard UTF-7 encoding.
|
|
11
|
+
*/
|
|
12
|
+
export declare function encodeAll(str: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* RFC 2152 UTF-7 decoding.
|
|
6
15
|
*/
|
|
7
|
-
export
|
|
8
|
-
export
|
|
16
|
+
export declare const decode: (str: string) => string;
|
|
17
|
+
export declare const imap: {
|
|
9
18
|
/**
|
|
10
19
|
* RFC 3501, section 5.1.3 UTF-7 encoding.
|
|
11
|
-
*
|
|
12
|
-
* @param {string} str
|
|
13
|
-
* @returns {string}
|
|
14
20
|
*/
|
|
15
|
-
|
|
21
|
+
encode(str: string): string;
|
|
16
22
|
/**
|
|
17
23
|
* RFC 3501, section 5.1.3 UTF-7 decoding.
|
|
18
|
-
*
|
|
19
|
-
* @param {string} str
|
|
20
|
-
* @returns {string}
|
|
21
24
|
*/
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
export function encode(str: string, mask?: string | null): string;
|
|
25
|
-
export function decode(str: string): string;
|
|
25
|
+
decode(str: string): string;
|
|
26
|
+
};
|
|
26
27
|
//# sourceMappingURL=utf7.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utf7.d.ts","sourceRoot":"","sources":["../../../lib/commands/utf7.
|
|
1
|
+
{"version":3,"file":"utf7.d.ts","sourceRoot":"","sources":["../../../lib/commands/utf7.ts"],"names":[],"mappings":"AAaA;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAmB,KAAK,MAAM,EAAE,OAAM,MAAM,GAAG,IAAW,KAAG,MAc/E,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM7C;AAED;;GAEG;AACH,eAAO,MAAM,MAAM,GAAmB,KAAK,MAAM,KAAG,MAKnD,CAAC;AAEF,eAAO,MAAM,IAAI;IACf;;OAEG;gBACS,MAAM,GAAG,MAAM;IAU3B;;OAEG;gBACS,MAAM,GAAG,MAAM;CAM5B,CAAC"}
|
|
@@ -3,78 +3,19 @@
|
|
|
3
3
|
* The code below has been adopted from https://www.npmjs.com/package/utf7
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.imap = exports.decode = exports.encode = void 0;
|
|
7
7
|
exports.encodeAll = encodeAll;
|
|
8
|
-
/**
|
|
9
|
-
* @param {number} length
|
|
10
|
-
* @returns {Buffer}
|
|
11
|
-
*/
|
|
12
|
-
function allocateAsciiBuffer(length) {
|
|
13
|
-
return Buffer.alloc(length, 'ascii');
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* @param {string} str
|
|
17
|
-
* @returns {string}
|
|
18
|
-
*/
|
|
19
|
-
function _encode(str) {
|
|
20
|
-
const b = allocateAsciiBuffer(str.length * 2);
|
|
21
|
-
for (let i = 0, bi = 0; i < str.length; i++) {
|
|
22
|
-
// Note that we can't simply convert a UTF-8 string to Base64 because
|
|
23
|
-
// UTF-8 uses a different encoding. In modified UTF-7, all characters
|
|
24
|
-
// are represented by their two byte Unicode ID.
|
|
25
|
-
const c = str.charCodeAt(i);
|
|
26
|
-
// Upper 8 bits shifted into lower 8 bits so that they fit into 1 byte.
|
|
27
|
-
b[bi++] = c >> 8;
|
|
28
|
-
// Lower 8 bits. Cut off the upper 8 bits so that they fit into 1 byte.
|
|
29
|
-
b[bi++] = c & 0xFF;
|
|
30
|
-
}
|
|
31
|
-
// Modified Base64 uses , instead of / and omits trailing =.
|
|
32
|
-
return b.toString('base64').replace(/=+$/, '');
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* @param {string} str
|
|
36
|
-
* @returns {Buffer}
|
|
37
|
-
*/
|
|
38
|
-
function allocateBase64Buffer(str) {
|
|
39
|
-
return Buffer.from(str, 'base64');
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* @param {string} str
|
|
43
|
-
* @returns {string}
|
|
44
|
-
*/
|
|
45
|
-
function _decode(str) {
|
|
46
|
-
const b = allocateBase64Buffer(str);
|
|
47
|
-
const r = [];
|
|
48
|
-
for (let i = 0; i < b.length;) {
|
|
49
|
-
// Calculate charcode from two adjacent bytes.
|
|
50
|
-
r.push(String.fromCharCode(b[i++] << 8 | b[i++]));
|
|
51
|
-
}
|
|
52
|
-
return r.join('');
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Escape RegEx from http://simonwillison.net/2006/Jan/20/escape/
|
|
56
|
-
*
|
|
57
|
-
* @param {string} chars
|
|
58
|
-
* @returns {string}
|
|
59
|
-
*/
|
|
60
|
-
function escape(chars) {
|
|
61
|
-
return chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
62
|
-
}
|
|
63
8
|
// Character classes defined by RFC 2152.
|
|
64
9
|
const setD = 'A-Za-z0-9' + escape(`'(),-./:?`);
|
|
65
10
|
const setO = escape(`!"#$%&*;<=>@[]^_'{|}`);
|
|
66
11
|
const setW = escape(` \r\n\t`);
|
|
67
12
|
// Stores compiled regexes for various replacement pattern.
|
|
68
|
-
/** @type {Record<string, RegExp>} */
|
|
69
13
|
const regexes = {};
|
|
70
14
|
const regexAll = new RegExp(`[^${setW}${setD}${setO}]+`, 'g');
|
|
71
|
-
exports.imap = {};
|
|
72
15
|
/**
|
|
73
16
|
* RFC 2152 UTF-7 encoding.
|
|
74
17
|
*
|
|
75
|
-
* @param
|
|
76
|
-
* @param {string?} mask
|
|
77
|
-
* @returns {string}
|
|
18
|
+
* @param mask Optional mask characters to exclude from encoding
|
|
78
19
|
*/
|
|
79
20
|
const encode = function encode(str, mask = null) {
|
|
80
21
|
// Generate a RegExp object from the string of mask characters.
|
|
@@ -92,9 +33,8 @@ const encode = function encode(str, mask = null) {
|
|
|
92
33
|
exports.encode = encode;
|
|
93
34
|
/**
|
|
94
35
|
* RFC 2152 UTF-7 encoding with all optionals.
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* @returns {string}
|
|
36
|
+
* Encodes all non-ASCII characters, including those that would normally
|
|
37
|
+
* be represented directly in standard UTF-7 encoding.
|
|
98
38
|
*/
|
|
99
39
|
function encodeAll(str) {
|
|
100
40
|
// We replace subsequent disallowed chars with their escape sequence.
|
|
@@ -102,26 +42,8 @@ function encodeAll(str) {
|
|
|
102
42
|
// + is represented by an empty sequence +-, otherwise call encode().
|
|
103
43
|
`+${chunk === '+' ? '' : _encode(chunk)}-`);
|
|
104
44
|
}
|
|
105
|
-
/**
|
|
106
|
-
* RFC 3501, section 5.1.3 UTF-7 encoding.
|
|
107
|
-
*
|
|
108
|
-
* @param {string} str
|
|
109
|
-
* @returns {string}
|
|
110
|
-
*/
|
|
111
|
-
exports.imap.encode = function encode(str) {
|
|
112
|
-
// All printable ASCII chars except for & must be represented by themselves.
|
|
113
|
-
// We replace subsequent non-representable chars with their escape sequence.
|
|
114
|
-
return str.replace(/&/g, '&-').replace(/[^\x20-\x7e]+/g, (chunk) => {
|
|
115
|
-
// & is represented by an empty sequence &-, otherwise call encode().
|
|
116
|
-
chunk = (chunk === '&' ? '' : _encode(chunk)).replace(/\//g, ',');
|
|
117
|
-
return `&${chunk}-`;
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
45
|
/**
|
|
121
46
|
* RFC 2152 UTF-7 decoding.
|
|
122
|
-
*
|
|
123
|
-
* @param {string} str
|
|
124
|
-
* @returns {string}
|
|
125
47
|
*/
|
|
126
48
|
const decode = function decode(str) {
|
|
127
49
|
return str.replace(/\+([A-Za-z0-9/]*)-?/gi, (_, chunk) =>
|
|
@@ -129,15 +51,75 @@ const decode = function decode(str) {
|
|
|
129
51
|
chunk === '' ? '+' : _decode(chunk));
|
|
130
52
|
};
|
|
131
53
|
exports.decode = decode;
|
|
54
|
+
exports.imap = {
|
|
55
|
+
/**
|
|
56
|
+
* RFC 3501, section 5.1.3 UTF-7 encoding.
|
|
57
|
+
*/
|
|
58
|
+
encode(str) {
|
|
59
|
+
// All printable ASCII chars except for & must be represented by themselves.
|
|
60
|
+
// We replace subsequent non-representable chars with their escape sequence.
|
|
61
|
+
return str.replace(/&/g, '&-').replace(/[^\x20-\x7e]+/g, (chunk) => {
|
|
62
|
+
// & is represented by an empty sequence &-, otherwise call encode().
|
|
63
|
+
chunk = (chunk === '&' ? '' : _encode(chunk)).replace(/\//g, ',');
|
|
64
|
+
return `&${chunk}-`;
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* RFC 3501, section 5.1.3 UTF-7 decoding.
|
|
69
|
+
*/
|
|
70
|
+
decode(str) {
|
|
71
|
+
return str.replace(/&([^-]*)-/g, (_, chunk) =>
|
|
72
|
+
// &- represents &.
|
|
73
|
+
chunk === '' ? '&' : _decode(chunk.replace(/,/g, '/')));
|
|
74
|
+
},
|
|
75
|
+
};
|
|
132
76
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* @param {string} str
|
|
136
|
-
* @returns {string}
|
|
77
|
+
* Allocates an ASCII buffer of the specified length.
|
|
137
78
|
*/
|
|
138
|
-
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
79
|
+
function allocateAsciiBuffer(length) {
|
|
80
|
+
return Buffer.alloc(length, 'ascii');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Encodes a string using modified UTF-7 encoding.
|
|
84
|
+
*/
|
|
85
|
+
function _encode(str) {
|
|
86
|
+
const b = allocateAsciiBuffer(str.length * 2);
|
|
87
|
+
for (let i = 0, bi = 0; i < str.length; i++) {
|
|
88
|
+
// Note that we can't simply convert a UTF-8 string to Base64 because
|
|
89
|
+
// UTF-8 uses a different encoding. In modified UTF-7, all characters
|
|
90
|
+
// are represented by their two byte Unicode ID.
|
|
91
|
+
const c = str.charCodeAt(i);
|
|
92
|
+
// Upper 8 bits shifted into lower 8 bits so that they fit into 1 byte.
|
|
93
|
+
b[bi++] = c >> 8;
|
|
94
|
+
// Lower 8 bits. Cut off the upper 8 bits so that they fit into 1 byte.
|
|
95
|
+
b[bi++] = c & 0xFF;
|
|
96
|
+
}
|
|
97
|
+
// Modified Base64 uses , instead of / and omits trailing =.
|
|
98
|
+
return b.toString('base64').replace(/=+$/, '');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Allocates a buffer from a base64 string.
|
|
102
|
+
*/
|
|
103
|
+
function allocateBase64Buffer(str) {
|
|
104
|
+
return Buffer.from(str, 'base64');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Decodes a string using modified UTF-7 encoding.
|
|
108
|
+
*/
|
|
109
|
+
function _decode(str) {
|
|
110
|
+
const b = allocateBase64Buffer(str);
|
|
111
|
+
const r = [];
|
|
112
|
+
for (let i = 0; i < b.length;) {
|
|
113
|
+
// Calculate charcode from two adjacent bytes.
|
|
114
|
+
r.push(String.fromCharCode(b[i++] << 8 | b[i++]));
|
|
115
|
+
}
|
|
116
|
+
return r.join('');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Escapes special regex characters.
|
|
120
|
+
* From http://simonwillison.net/2006/Jan/20/escape/
|
|
121
|
+
*/
|
|
122
|
+
function escape(chars) {
|
|
123
|
+
return chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
124
|
+
}
|
|
143
125
|
//# sourceMappingURL=utf7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utf7.js","sourceRoot":"","sources":["../../../lib/commands/utf7.
|
|
1
|
+
{"version":3,"file":"utf7.js","sourceRoot":"","sources":["../../../lib/commands/utf7.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
import { SubProcess } from 'teen_process';
|
|
8
8
|
import B from 'bluebird';
|
|
9
9
|
import { LOG_PREFIX } from '../logger.js';
|
|
10
|
+
import type { SettingsApp } from '../client.js';
|
|
11
|
+
import type { Location } from './types.js';
|
|
10
12
|
|
|
11
13
|
const DEFAULT_SATELLITES_COUNT = 12;
|
|
12
14
|
const DEFAULT_ALTITUDE = 0.0;
|
|
@@ -18,42 +20,23 @@ const GPS_CACHE_REFRESHED_LOGS = [
|
|
|
18
20
|
|
|
19
21
|
const GPS_COORDINATES_PATTERN = /data="(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)"/;
|
|
20
22
|
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {Object} Location
|
|
23
|
-
* @property {number|string} longitude - Valid longitude value.
|
|
24
|
-
* @property {number|string} latitude - Valid latitude value.
|
|
25
|
-
* @property {number|string|null} [altitude] - Valid altitude value.
|
|
26
|
-
* @property {number|string|null} [satellites=12] - Number of satellites being tracked (1-12).
|
|
27
|
-
* This value is ignored on real devices.
|
|
28
|
-
* @property {number|string|null} [speed] - Valid speed value.
|
|
29
|
-
* https://developer.android.com/reference/android/location/Location#setSpeed(float)
|
|
30
|
-
* @property {number|string|null} [bearing] - Valid bearing value.
|
|
31
|
-
* https://developer.android.com/reference/android/location/Location#setBearing(float)
|
|
32
|
-
* @property {number|string|null} [accuracy] - Valid accuracy value.
|
|
33
|
-
* https://developer.android.com/reference/android/location/Location#setAccuracy(float),
|
|
34
|
-
* https://developer.android.com/reference/android/location/Criteria
|
|
35
|
-
* Should be greater than 0.0 meters/second for real devices or 0.0 knots
|
|
36
|
-
* for emulators.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
23
|
/**
|
|
40
24
|
* Emulate geolocation coordinates on the device under test.
|
|
25
|
+
* The `altitude` value is ignored while mocking the position.
|
|
41
26
|
*
|
|
42
|
-
* @
|
|
43
|
-
* @param
|
|
44
|
-
*
|
|
45
|
-
* @param {boolean} [isEmulator=false] - Set it to true if the device under test
|
|
46
|
-
* is an emulator rather than a real device.
|
|
27
|
+
* @param location - Location object containing coordinates and optional metadata
|
|
28
|
+
* @param isEmulator - Set it to true if the device under test is an emulator rather than a real device
|
|
29
|
+
* @throws {Error} If required location values are missing or invalid
|
|
47
30
|
*/
|
|
48
|
-
export async function setGeoLocation
|
|
49
|
-
const formatLocationValue = (valueName, isRequired = true) => {
|
|
31
|
+
export async function setGeoLocation(this: SettingsApp, location: Location, isEmulator = false): Promise<void> {
|
|
32
|
+
const formatLocationValue = (valueName: keyof Location, isRequired = true): string | null => {
|
|
50
33
|
if (_.isNil(location[valueName])) {
|
|
51
34
|
if (isRequired) {
|
|
52
35
|
throw new Error(`${valueName} must be provided`);
|
|
53
36
|
}
|
|
54
37
|
return null;
|
|
55
38
|
}
|
|
56
|
-
const floatValue = parseFloat(location[valueName]);
|
|
39
|
+
const floatValue = parseFloat(String(location[valueName]));
|
|
57
40
|
if (!isNaN(floatValue)) {
|
|
58
41
|
return `${_.ceil(floatValue, 5)}`;
|
|
59
42
|
}
|
|
@@ -63,15 +46,14 @@ export async function setGeoLocation (location, isEmulator = false) {
|
|
|
63
46
|
}
|
|
64
47
|
return null;
|
|
65
48
|
};
|
|
66
|
-
const longitude =
|
|
67
|
-
const latitude =
|
|
49
|
+
const longitude = formatLocationValue('longitude') as string;
|
|
50
|
+
const latitude = formatLocationValue('latitude') as string;
|
|
68
51
|
const altitude = formatLocationValue('altitude', false);
|
|
69
52
|
const speed = formatLocationValue('speed', false);
|
|
70
53
|
const bearing = formatLocationValue('bearing', false);
|
|
71
54
|
const accuracy = formatLocationValue('accuracy', false);
|
|
72
55
|
if (isEmulator) {
|
|
73
|
-
|
|
74
|
-
const args = [longitude, latitude];
|
|
56
|
+
const args: string[] = [longitude, latitude];
|
|
75
57
|
if (!_.isNil(altitude)) {
|
|
76
58
|
args.push(altitude);
|
|
77
59
|
}
|
|
@@ -96,7 +78,7 @@ export async function setGeoLocation (location, isEmulator = false) {
|
|
|
96
78
|
// A workaround for https://code.google.com/p/android/issues/detail?id=206180
|
|
97
79
|
await this.adb.adbExec(['emu', 'geo', 'fix', ...(args.map((arg) => arg.replace('.', ',')))]);
|
|
98
80
|
} else {
|
|
99
|
-
const args = [
|
|
81
|
+
const args: string[] = [
|
|
100
82
|
'am', 'start-foreground-service',
|
|
101
83
|
'-e', 'longitude', longitude,
|
|
102
84
|
'-e', 'latitude', latitude,
|
|
@@ -130,11 +112,10 @@ export async function setGeoLocation (location, isEmulator = false) {
|
|
|
130
112
|
/**
|
|
131
113
|
* Get the current cached GPS location from the device under test.
|
|
132
114
|
*
|
|
133
|
-
* @
|
|
134
|
-
* @returns {Promise<Location>} The current location
|
|
115
|
+
* @returns The current location
|
|
135
116
|
* @throws {Error} If the current location cannot be retrieved
|
|
136
117
|
*/
|
|
137
|
-
export async function getGeoLocation
|
|
118
|
+
export async function getGeoLocation(this: SettingsApp): Promise<Location> {
|
|
138
119
|
const output = await this.checkBroadcast([
|
|
139
120
|
'-n', LOCATION_RECEIVER,
|
|
140
121
|
'-a', LOCATION_RETRIEVAL_ACTION,
|
|
@@ -144,7 +125,7 @@ export async function getGeoLocation () {
|
|
|
144
125
|
if (!match) {
|
|
145
126
|
throw new Error(`Cannot parse the actual location values from the command output: ${output}`);
|
|
146
127
|
}
|
|
147
|
-
const location = {
|
|
128
|
+
const location: Location = {
|
|
148
129
|
latitude: match[1],
|
|
149
130
|
longitude: match[2],
|
|
150
131
|
altitude: match[3],
|
|
@@ -160,18 +141,15 @@ export async function getGeoLocation () {
|
|
|
160
141
|
* LocationManager is used the device API level must be at
|
|
161
142
|
* version 30 (Android R) or higher.
|
|
162
143
|
*
|
|
163
|
-
* @
|
|
164
|
-
*
|
|
165
|
-
* to block until GPS cache is refreshed. Providing zero or a negative
|
|
166
|
-
* value to it skips waiting completely.
|
|
167
|
-
*
|
|
144
|
+
* @param timeoutMs The maximum number of milliseconds to block until GPS cache is refreshed.
|
|
145
|
+
* Providing zero or a negative value to it skips waiting completely.
|
|
168
146
|
* @throws {Error} If the GPS cache cannot be refreshed.
|
|
169
147
|
*/
|
|
170
|
-
export async function refreshGeoLocationCache
|
|
148
|
+
export async function refreshGeoLocationCache(this: SettingsApp, timeoutMs = 20000): Promise<void> {
|
|
171
149
|
await this.requireRunning({shouldRestoreCurrentApp: true});
|
|
172
150
|
|
|
173
|
-
let logcatMonitor;
|
|
174
|
-
let monitoringPromise;
|
|
151
|
+
let logcatMonitor: SubProcess | undefined;
|
|
152
|
+
let monitoringPromise: Promise<void> | undefined;
|
|
175
153
|
|
|
176
154
|
if (timeoutMs > 0) {
|
|
177
155
|
const cmd = [
|
|
@@ -183,11 +161,12 @@ export async function refreshGeoLocationCache (timeoutMs = 20000) {
|
|
|
183
161
|
`Please make sure the device under test has Appium Settings app installed and running. ` +
|
|
184
162
|
`Also, it is required that the device has Google Play Services installed or is running ` +
|
|
185
163
|
`Android 10+ otherwise.`;
|
|
186
|
-
|
|
164
|
+
const monitor = logcatMonitor;
|
|
165
|
+
monitoringPromise = new B<void>((resolve, reject) => {
|
|
187
166
|
setTimeout(() => reject(new Error(timeoutErrorMsg)), timeoutMs);
|
|
188
167
|
|
|
189
|
-
|
|
190
|
-
['lines-stderr', 'lines-stdout'].map((evt) =>
|
|
168
|
+
monitor.on('exit', () => reject(new Error(timeoutErrorMsg)));
|
|
169
|
+
(['lines-stderr', 'lines-stdout'] as const).map((evt) => monitor.on(evt, (lines: string[]) => {
|
|
191
170
|
if (lines.some((line) => GPS_CACHE_REFRESHED_LOGS.some((x) => line.includes(x)))) {
|
|
192
171
|
resolve();
|
|
193
172
|
}
|
|
@@ -9,25 +9,60 @@ import {
|
|
|
9
9
|
RECORDING_ACTION_STOP,
|
|
10
10
|
RECORDING_ACTIVITY_NAME,
|
|
11
11
|
RECORDING_SERVICE_NAME,
|
|
12
|
-
} from '../constants';
|
|
12
|
+
} from '../constants.js';
|
|
13
|
+
import type { ADB } from 'appium-adb';
|
|
14
|
+
import type { SettingsApp } from '../client.js';
|
|
15
|
+
import type { StartMediaProjectionRecordingOpts } from './types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new instance of the MediaProjection-based recorder.
|
|
19
|
+
* The recorder only works since Android API 29+
|
|
20
|
+
*
|
|
21
|
+
* @returns The recorder instance
|
|
22
|
+
*/
|
|
23
|
+
export function makeMediaProjectionRecorder(this: SettingsApp): MediaProjectionRecorder {
|
|
24
|
+
return new MediaProjectionRecorder(this.adb);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Adjusts the necessary permissions for the Media Projection-based recording service.
|
|
29
|
+
* This method only applies to devices running Android API 29 or higher.
|
|
30
|
+
*
|
|
31
|
+
* @returns True if permissions were adjusted, false if device API level is below 29
|
|
32
|
+
*/
|
|
33
|
+
export async function adjustMediaProjectionServicePermissions(this: SettingsApp): Promise<boolean> {
|
|
34
|
+
if (await this.adb.getApiLevel() >= 29) {
|
|
35
|
+
await this.adb.shell(['appops', 'set', SETTINGS_HELPER_ID, 'PROJECT_MEDIA', 'allow']);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
const RECORDING_STARTUP_TIMEOUT_MS = 3 * 1000;
|
|
15
42
|
const RECORDING_STOP_TIMEOUT_MS = 3 * 1000;
|
|
16
43
|
const RECORDINGS_ROOT = `/storage/emulated/0/Android/data/${SETTINGS_HELPER_ID}/files`;
|
|
17
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Media projection recorder for capturing device screen recordings.
|
|
47
|
+
* This class provides methods to start, stop, and manage screen recordings
|
|
48
|
+
* using Android's MediaProjection API (API 29+).
|
|
49
|
+
*/
|
|
18
50
|
class MediaProjectionRecorder {
|
|
51
|
+
private readonly adb: ADB;
|
|
52
|
+
|
|
19
53
|
/**
|
|
20
|
-
*
|
|
54
|
+
* Creates a new MediaProjectionRecorder instance.
|
|
55
|
+
*
|
|
56
|
+
* @param adb - ADB instance for device communication
|
|
21
57
|
*/
|
|
22
|
-
constructor(adb) {
|
|
58
|
+
constructor(adb: ADB) {
|
|
23
59
|
this.adb = adb;
|
|
24
60
|
}
|
|
25
61
|
|
|
26
62
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @returns {Promise<boolean>}
|
|
63
|
+
* Checks if the recording is currently running.
|
|
29
64
|
*/
|
|
30
|
-
async isRunning() {
|
|
65
|
+
async isRunning(): Promise<boolean> {
|
|
31
66
|
const stdout = await this.adb.shell([
|
|
32
67
|
'dumpsys',
|
|
33
68
|
'activity',
|
|
@@ -38,18 +73,21 @@ class MediaProjectionRecorder {
|
|
|
38
73
|
}
|
|
39
74
|
|
|
40
75
|
/**
|
|
76
|
+
* Starts the media projection recording.
|
|
77
|
+
* If a recording is already running, this method will return false without starting a new one.
|
|
41
78
|
*
|
|
42
|
-
* @param
|
|
43
|
-
* @returns
|
|
79
|
+
* @param opts Recording options including filename, resolution, duration, and priority
|
|
80
|
+
* @returns True if recording was started successfully, false if already running
|
|
81
|
+
* @throws {Error} If recording fails to start within the timeout period
|
|
44
82
|
*/
|
|
45
|
-
async start(opts = {}) {
|
|
83
|
+
async start(opts: StartMediaProjectionRecordingOpts = {}): Promise<boolean> {
|
|
46
84
|
if (await this.isRunning()) {
|
|
47
85
|
return false;
|
|
48
86
|
}
|
|
49
87
|
|
|
50
88
|
await this.cleanup();
|
|
51
89
|
const {filename, maxDurationSec, priority, resolution} = opts;
|
|
52
|
-
const args = ['am', 'start', '-n', RECORDING_ACTIVITY_NAME, '-a', RECORDING_ACTION_START];
|
|
90
|
+
const args: string[] = ['am', 'start', '-n', RECORDING_ACTIVITY_NAME, '-a', RECORDING_ACTION_START];
|
|
53
91
|
if (filename) {
|
|
54
92
|
args.push('--es', 'filename', filename);
|
|
55
93
|
}
|
|
@@ -63,7 +101,7 @@ class MediaProjectionRecorder {
|
|
|
63
101
|
args.push('--es', 'resolution', resolution);
|
|
64
102
|
}
|
|
65
103
|
await this.adb.shell(args);
|
|
66
|
-
await new B((resolve, reject) => {
|
|
104
|
+
await new B<void>((resolve, reject) => {
|
|
67
105
|
setTimeout(async () => {
|
|
68
106
|
if (!(await this.isRunning())) {
|
|
69
107
|
return reject(
|
|
@@ -80,17 +118,18 @@ class MediaProjectionRecorder {
|
|
|
80
118
|
}
|
|
81
119
|
|
|
82
120
|
/**
|
|
83
|
-
*
|
|
121
|
+
* Cleans up old recording files.
|
|
84
122
|
*/
|
|
85
|
-
async cleanup() {
|
|
123
|
+
async cleanup(): Promise<void> {
|
|
86
124
|
await this.adb.shell([`rm -f ${RECORDINGS_ROOT}/*`]);
|
|
87
125
|
}
|
|
88
126
|
|
|
89
127
|
/**
|
|
128
|
+
* Pulls the most recent recording file from the device.
|
|
90
129
|
*
|
|
91
|
-
* @returns
|
|
130
|
+
* @returns Path to the pulled file, or null if no recordings exist
|
|
92
131
|
*/
|
|
93
|
-
async pullRecent() {
|
|
132
|
+
async pullRecent(): Promise<string | null> {
|
|
94
133
|
const recordings = await this.adb.ls(RECORDINGS_ROOT, ['-tr']);
|
|
95
134
|
if (_.isEmpty(recordings)) {
|
|
96
135
|
return null;
|
|
@@ -104,9 +143,12 @@ class MediaProjectionRecorder {
|
|
|
104
143
|
}
|
|
105
144
|
|
|
106
145
|
/**
|
|
107
|
-
*
|
|
146
|
+
* Stops the current recording.
|
|
147
|
+
*
|
|
148
|
+
* @returns True if recording was stopped successfully, false if no recording was running
|
|
149
|
+
* @throws {Error} If the recording fails to stop within the timeout period
|
|
108
150
|
*/
|
|
109
|
-
async stop() {
|
|
151
|
+
async stop(): Promise<boolean> {
|
|
110
152
|
if (!(await this.isRunning())) {
|
|
111
153
|
return false;
|
|
112
154
|
}
|
|
@@ -133,49 +175,3 @@ class MediaProjectionRecorder {
|
|
|
133
175
|
return true;
|
|
134
176
|
}
|
|
135
177
|
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Creates a new instance of the MediaProjection-based recorder
|
|
139
|
-
* The recorder only works since Android API 29+
|
|
140
|
-
*
|
|
141
|
-
* @this {import('../client').SettingsApp}
|
|
142
|
-
* @returns {MediaProjectionRecorder} The recorder instance
|
|
143
|
-
*/
|
|
144
|
-
export function makeMediaProjectionRecorder() {
|
|
145
|
-
return new MediaProjectionRecorder(this.adb);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Adjusts the necessary permissions for the
|
|
150
|
-
* Media Projection-based recording service
|
|
151
|
-
*
|
|
152
|
-
* @this {import('../client').SettingsApp}
|
|
153
|
-
* @returns {Promise<boolean>} If the permissions adjustment has actually been made
|
|
154
|
-
*/
|
|
155
|
-
export async function adjustMediaProjectionServicePermissions() {
|
|
156
|
-
if (await this.adb.getApiLevel() >= 29) {
|
|
157
|
-
await this.adb.shell(['appops', 'set', SETTINGS_HELPER_ID, 'PROJECT_MEDIA', 'allow']);
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @typedef {import('appium-adb').ADB} ADB
|
|
165
|
-
*/
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* @typedef {Object} StartMediaProjectionRecordingOpts
|
|
169
|
-
* @property {string} [resolution] Maximum supported resolution on-device (Detected automatically by the app
|
|
170
|
-
* itself), which usually equals to Full HD 1920x1080 on most phones however
|
|
171
|
-
* you can change it to following supported resolutions as well: "1920x1080",
|
|
172
|
-
* "1280x720", "720x480", "320x240", "176x144".
|
|
173
|
-
* @property {number} [maxDurationSec=900] Maximum allowed duration is 15 minutes; you can increase it if your test
|
|
174
|
-
* takes longer than that.
|
|
175
|
-
* @property {'high' | 'normal' | 'low'} [priority='high'] Recording thread priority.
|
|
176
|
-
* If you face performance drops during testing with recording enabled, you
|
|
177
|
-
* can reduce recording priority
|
|
178
|
-
* @property {string} [filename] You can type recording video file name as you want, but recording currently
|
|
179
|
-
* supports only "mp4" format so your filename must end with ".mp4". An
|
|
180
|
-
* invalid file name will fail to start the recording.
|
|
181
|
-
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Location object containing geolocation coordinates and optional metadata.
|
|
3
|
+
*/
|
|
4
|
+
export interface Location {
|
|
5
|
+
longitude: number | string;
|
|
6
|
+
latitude: number | string;
|
|
7
|
+
altitude?: number | string | null;
|
|
8
|
+
/** Number of satellites being tracked (1-12). This value is ignored on real devices. */
|
|
9
|
+
satellites?: number | string | null;
|
|
10
|
+
/** Valid speed value. https://developer.android.com/reference/android/location/Location#setSpeed(float) */
|
|
11
|
+
speed?: number | string | null;
|
|
12
|
+
/** Valid bearing value. https://developer.android.com/reference/android/location/Location#setBearing(float) */
|
|
13
|
+
bearing?: number | string | null;
|
|
14
|
+
/** Valid accuracy value. https://developer.android.com/reference/android/location/Location#setAccuracy(float), https://developer.android.com/reference/android/location/Criteria. Should be greater than 0.0 meters/second for real devices or 0.0 knots for emulators. */
|
|
15
|
+
accuracy?: number | string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for starting a media projection recording session.
|
|
20
|
+
*/
|
|
21
|
+
export interface StartMediaProjectionRecordingOpts {
|
|
22
|
+
/** Maximum supported resolution on-device (Detected automatically by the app
|
|
23
|
+
* itself), which usually equals to Full HD 1920x1080 on most phones however
|
|
24
|
+
* you can change it to following supported resolutions as well: "1920x1080",
|
|
25
|
+
* "1280x720", "720x480", "320x240", "176x144". */
|
|
26
|
+
resolution?: string;
|
|
27
|
+
/** Maximum allowed duration is 15 minutes; you can increase it if your test
|
|
28
|
+
* takes longer than that. */
|
|
29
|
+
maxDurationSec?: number;
|
|
30
|
+
/** Recording thread priority.
|
|
31
|
+
* If you face performance drops during testing with recording enabled, you
|
|
32
|
+
* can reduce recording priority */
|
|
33
|
+
priority?: 'high' | 'normal' | 'low';
|
|
34
|
+
/** You can type recording video file name as you want, but recording currently
|
|
35
|
+
* supports only "mp4" format so your filename must end with ".mp4". An
|
|
36
|
+
* invalid file name will fail to start the recording. */
|
|
37
|
+
filename?: string;
|
|
38
|
+
}
|