appium-adb 14.3.2 → 14.3.4
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/build/lib/adb.d.ts +9 -9
- package/build/lib/adb.d.ts.map +1 -1
- package/build/lib/adb.js +61 -61
- package/build/lib/adb.js.map +1 -1
- package/build/lib/logcat.d.ts.map +1 -1
- package/build/lib/logcat.js +43 -37
- package/build/lib/logcat.js.map +1 -1
- package/build/lib/tools/aab-utils.d.ts.map +1 -1
- package/build/lib/tools/aab-utils.js +1 -2
- package/build/lib/tools/aab-utils.js.map +1 -1
- package/build/lib/tools/apk-utils.d.ts.map +1 -1
- package/build/lib/tools/apk-utils.js +29 -29
- package/build/lib/tools/apk-utils.js.map +1 -1
- package/build/lib/tools/apks-utils.d.ts.map +1 -1
- package/build/lib/tools/apks-utils.js +44 -45
- package/build/lib/tools/apks-utils.js.map +1 -1
- package/build/lib/tools/app-commands.d.ts +11 -11
- package/build/lib/tools/app-commands.d.ts.map +1 -1
- package/build/lib/tools/app-commands.js +0 -2
- package/build/lib/tools/app-commands.js.map +1 -1
- package/build/lib/tools/device-settings.d.ts.map +1 -1
- package/build/lib/tools/device-settings.js +14 -15
- package/build/lib/tools/device-settings.js.map +1 -1
- package/build/lib/tools/emulator-commands.d.ts.map +1 -1
- package/build/lib/tools/emulator-commands.js +43 -44
- package/build/lib/tools/emulator-commands.js.map +1 -1
- package/build/lib/tools/fs-commands.d.ts.map +1 -1
- package/build/lib/tools/fs-commands.js +3 -3
- package/build/lib/tools/fs-commands.js.map +1 -1
- package/build/lib/tools/keyboard-commands.d.ts.map +1 -1
- package/build/lib/tools/keyboard-commands.js +1 -5
- package/build/lib/tools/keyboard-commands.js.map +1 -1
- package/build/lib/tools/lockmgmt.d.ts.map +1 -1
- package/build/lib/tools/lockmgmt.js +72 -73
- package/build/lib/tools/lockmgmt.js.map +1 -1
- package/build/lib/tools/process-commands.d.ts.map +1 -1
- package/build/lib/tools/process-commands.js +1 -2
- package/build/lib/tools/process-commands.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts +6 -6
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +11 -14
- package/build/lib/tools/system-calls.js.map +1 -1
- package/lib/adb.ts +69 -69
- package/lib/logcat.ts +47 -44
- package/lib/tools/aab-utils.ts +1 -2
- package/lib/tools/apk-utils.ts +37 -37
- package/lib/tools/apks-utils.ts +50 -51
- package/lib/tools/app-commands.ts +12 -16
- package/lib/tools/device-settings.ts +15 -16
- package/lib/tools/emulator-commands.ts +51 -52
- package/lib/tools/fs-commands.ts +4 -4
- package/lib/tools/keyboard-commands.ts +2 -3
- package/lib/tools/lockmgmt.ts +85 -86
- package/lib/tools/process-commands.ts +1 -2
- package/lib/tools/system-calls.ts +12 -17
- package/package.json +1 -3
package/lib/tools/apk-utils.ts
CHANGED
|
@@ -583,43 +583,6 @@ export async function getApkInfo(this: ADB, appPath: string): Promise<AppInfo |
|
|
|
583
583
|
|
|
584
584
|
// #region Private functions
|
|
585
585
|
|
|
586
|
-
/**
|
|
587
|
-
* Formats the config marker, which is then passed to parse.. methods
|
|
588
|
-
* to make it compatible with resource formats generated by aapt(2) tool
|
|
589
|
-
*
|
|
590
|
-
* @param configsGetter The function whose result is a list
|
|
591
|
-
* of apk configs
|
|
592
|
-
* @param desiredMarker The desired config marker value
|
|
593
|
-
* @param defaultMarker The default config marker value
|
|
594
|
-
* @returns The formatted config marker
|
|
595
|
-
*/
|
|
596
|
-
async function formatConfigMarker(
|
|
597
|
-
configsGetter: () => Promise<string[]>,
|
|
598
|
-
desiredMarker: string | null,
|
|
599
|
-
defaultMarker: string,
|
|
600
|
-
): Promise<string> {
|
|
601
|
-
let configMarker = desiredMarker || defaultMarker;
|
|
602
|
-
if (configMarker.includes('-') && !configMarker.includes('-r')) {
|
|
603
|
-
configMarker = configMarker.replace('-', '-r');
|
|
604
|
-
}
|
|
605
|
-
const configs = await configsGetter();
|
|
606
|
-
log.debug(`Resource configurations: ${JSON.stringify(configs)}`);
|
|
607
|
-
// Assume the 'en' configuration is the default one
|
|
608
|
-
if (
|
|
609
|
-
configMarker.toLowerCase().startsWith('en') &&
|
|
610
|
-
!configs.some((x) => x.trim() === configMarker)
|
|
611
|
-
) {
|
|
612
|
-
log.debug(
|
|
613
|
-
`Resource configuration name '${configMarker}' is unknown. ` +
|
|
614
|
-
`Replacing it with '${defaultMarker}'`,
|
|
615
|
-
);
|
|
616
|
-
configMarker = defaultMarker;
|
|
617
|
-
} else {
|
|
618
|
-
log.debug(`Selected configuration: '${configMarker}'`);
|
|
619
|
-
}
|
|
620
|
-
return configMarker;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
586
|
/**
|
|
624
587
|
* Parses apk strings from aapt2 tool output
|
|
625
588
|
*
|
|
@@ -812,4 +775,41 @@ export function parseAaptStrings(rawOutput: string, configMarker: string): Strin
|
|
|
812
775
|
return apkStrings;
|
|
813
776
|
}
|
|
814
777
|
|
|
778
|
+
/**
|
|
779
|
+
* Formats the config marker, which is then passed to parse.. methods
|
|
780
|
+
* to make it compatible with resource formats generated by aapt(2) tool
|
|
781
|
+
*
|
|
782
|
+
* @param configsGetter The function whose result is a list
|
|
783
|
+
* of apk configs
|
|
784
|
+
* @param desiredMarker The desired config marker value
|
|
785
|
+
* @param defaultMarker The default config marker value
|
|
786
|
+
* @returns The formatted config marker
|
|
787
|
+
*/
|
|
788
|
+
async function formatConfigMarker(
|
|
789
|
+
configsGetter: () => Promise<string[]>,
|
|
790
|
+
desiredMarker: string | null,
|
|
791
|
+
defaultMarker: string,
|
|
792
|
+
): Promise<string> {
|
|
793
|
+
let configMarker = desiredMarker || defaultMarker;
|
|
794
|
+
if (configMarker.includes('-') && !configMarker.includes('-r')) {
|
|
795
|
+
configMarker = configMarker.replace('-', '-r');
|
|
796
|
+
}
|
|
797
|
+
const configs = await configsGetter();
|
|
798
|
+
log.debug(`Resource configurations: ${JSON.stringify(configs)}`);
|
|
799
|
+
// Assume the 'en' configuration is the default one
|
|
800
|
+
if (
|
|
801
|
+
configMarker.toLowerCase().startsWith('en') &&
|
|
802
|
+
!configs.some((x) => x.trim() === configMarker)
|
|
803
|
+
) {
|
|
804
|
+
log.debug(
|
|
805
|
+
`Resource configuration name '${configMarker}' is unknown. ` +
|
|
806
|
+
`Replacing it with '${defaultMarker}'`,
|
|
807
|
+
);
|
|
808
|
+
configMarker = defaultMarker;
|
|
809
|
+
} else {
|
|
810
|
+
log.debug(`Selected configuration: '${configMarker}'`);
|
|
811
|
+
}
|
|
812
|
+
return configMarker;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
815
|
// #endregion
|
package/lib/tools/apks-utils.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {fs, tempDir, util} from '@appium/support';
|
|
|
6
6
|
import {LRUCache} from 'lru-cache';
|
|
7
7
|
import {getJavaForOs, unzipFile, buildInstallArgs, APK_INSTALL_TIMEOUT} from '../helpers';
|
|
8
8
|
import AsyncLock from 'async-lock';
|
|
9
|
-
import B from 'bluebird';
|
|
10
9
|
import type {ADB} from '../adb';
|
|
11
10
|
import type {InstallMultipleApksOptions, InstallApksOptions, StringRecord} from './types';
|
|
12
11
|
|
|
@@ -39,55 +38,6 @@ process.on('exit', () => {
|
|
|
39
38
|
}
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
/**
|
|
43
|
-
* Extracts the particular apks package into a temporary folder,
|
|
44
|
-
* finds and returns the full path to the file contained in this apk.
|
|
45
|
-
* The resulting temporary path, where the .apks file has been extracted,
|
|
46
|
-
* will be stored into the internal LRU cache for better performance.
|
|
47
|
-
*
|
|
48
|
-
* @param apks - The full path to the .apks file
|
|
49
|
-
* @param dstPath - The relative path to the destination file,
|
|
50
|
-
* which is going to be extracted, where each path component is an array item
|
|
51
|
-
* @returns Full path to the extracted file
|
|
52
|
-
* @throws {Error} If the requested item does not exist in the extracted archive or the provides
|
|
53
|
-
* apks file is not a valid bundle
|
|
54
|
-
*/
|
|
55
|
-
async function extractFromApks(apks: string, dstPath: string | string[]): Promise<string> {
|
|
56
|
-
const normalizedDstPath = _.isArray(dstPath) ? dstPath : [dstPath];
|
|
57
|
-
|
|
58
|
-
return await APKS_CACHE_GUARD.acquire(apks, async () => {
|
|
59
|
-
// It might be that the original file has been replaced,
|
|
60
|
-
// so we need to keep the hash sums instead of the actual file paths
|
|
61
|
-
// as caching keys
|
|
62
|
-
const apksHash = await fs.hash(apks);
|
|
63
|
-
log.debug(`Calculated '${apks}' hash: ${apksHash}`);
|
|
64
|
-
|
|
65
|
-
if (APKS_CACHE.has(apksHash)) {
|
|
66
|
-
const cachedRoot = APKS_CACHE.get(apksHash);
|
|
67
|
-
if (cachedRoot) {
|
|
68
|
-
const resultPath = path.resolve(cachedRoot, ...normalizedDstPath);
|
|
69
|
-
if (await fs.exists(resultPath)) {
|
|
70
|
-
return resultPath;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
APKS_CACHE.delete(apksHash);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const tmpRoot = await tempDir.openDir();
|
|
77
|
-
log.debug(`Unpacking application bundle at '${apks}' to '${tmpRoot}'`);
|
|
78
|
-
await unzipFile(apks, tmpRoot);
|
|
79
|
-
const resultPath = path.resolve(tmpRoot, ...normalizedDstPath);
|
|
80
|
-
if (!(await fs.exists(resultPath))) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`${normalizedDstPath.join(path.sep)} cannot be found in '${apks}' bundle. ` +
|
|
83
|
-
`Does the archive contain a valid application bundle?`,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
APKS_CACHE.set(apksHash, tmpRoot);
|
|
87
|
-
return resultPath;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
41
|
/**
|
|
92
42
|
* Executes bundletool utility with given arguments and returns the actual stdout
|
|
93
43
|
*
|
|
@@ -206,7 +156,7 @@ export async function installApks(
|
|
|
206
156
|
if (grantPermissions) {
|
|
207
157
|
tasks.push(this.getApkInfo(apks));
|
|
208
158
|
}
|
|
209
|
-
const [, apkInfo] = await
|
|
159
|
+
const [, apkInfo] = await Promise.all(tasks);
|
|
210
160
|
if (grantPermissions && apkInfo) {
|
|
211
161
|
// TODO: Simplify it after https://github.com/google/bundletool/issues/246 is implemented
|
|
212
162
|
await this.grantAllPermissions(apkInfo.name);
|
|
@@ -275,3 +225,52 @@ export async function extractLanguageApk(
|
|
|
275
225
|
export function isTestPackageOnlyError(output: string): boolean {
|
|
276
226
|
return /\[INSTALL_FAILED_TEST_ONLY\]/.test(output);
|
|
277
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Extracts the particular apks package into a temporary folder,
|
|
231
|
+
* finds and returns the full path to the file contained in this apk.
|
|
232
|
+
* The resulting temporary path, where the .apks file has been extracted,
|
|
233
|
+
* will be stored into the internal LRU cache for better performance.
|
|
234
|
+
*
|
|
235
|
+
* @param apks - The full path to the .apks file
|
|
236
|
+
* @param dstPath - The relative path to the destination file,
|
|
237
|
+
* which is going to be extracted, where each path component is an array item
|
|
238
|
+
* @returns Full path to the extracted file
|
|
239
|
+
* @throws {Error} If the requested item does not exist in the extracted archive or the provides
|
|
240
|
+
* apks file is not a valid bundle
|
|
241
|
+
*/
|
|
242
|
+
async function extractFromApks(apks: string, dstPath: string | string[]): Promise<string> {
|
|
243
|
+
const normalizedDstPath = _.isArray(dstPath) ? dstPath : [dstPath];
|
|
244
|
+
|
|
245
|
+
return await APKS_CACHE_GUARD.acquire(apks, async () => {
|
|
246
|
+
// It might be that the original file has been replaced,
|
|
247
|
+
// so we need to keep the hash sums instead of the actual file paths
|
|
248
|
+
// as caching keys
|
|
249
|
+
const apksHash = await fs.hash(apks);
|
|
250
|
+
log.debug(`Calculated '${apks}' hash: ${apksHash}`);
|
|
251
|
+
|
|
252
|
+
if (APKS_CACHE.has(apksHash)) {
|
|
253
|
+
const cachedRoot = APKS_CACHE.get(apksHash);
|
|
254
|
+
if (cachedRoot) {
|
|
255
|
+
const resultPath = path.resolve(cachedRoot, ...normalizedDstPath);
|
|
256
|
+
if (await fs.exists(resultPath)) {
|
|
257
|
+
return resultPath;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
APKS_CACHE.delete(apksHash);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const tmpRoot = await tempDir.openDir();
|
|
264
|
+
log.debug(`Unpacking application bundle at '${apks}' to '${tmpRoot}'`);
|
|
265
|
+
await unzipFile(apks, tmpRoot);
|
|
266
|
+
const resultPath = path.resolve(tmpRoot, ...normalizedDstPath);
|
|
267
|
+
if (!(await fs.exists(resultPath))) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`${normalizedDstPath.join(path.sep)} cannot be found in '${apks}' bundle. ` +
|
|
270
|
+
`Does the archive contain a valid application bundle?`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
APKS_CACHE.set(apksHash, tmpRoot);
|
|
274
|
+
return resultPath;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
@@ -35,6 +35,18 @@ const LAUNCHER_CATEGORY = 'android.intent.category.LAUNCHER';
|
|
|
35
35
|
|
|
36
36
|
// Public methods
|
|
37
37
|
|
|
38
|
+
export interface StartCmdOptions {
|
|
39
|
+
user?: number | string;
|
|
40
|
+
waitForLaunch?: boolean;
|
|
41
|
+
pkg?: string;
|
|
42
|
+
activity?: string;
|
|
43
|
+
action?: string;
|
|
44
|
+
category?: string;
|
|
45
|
+
stopApp?: boolean;
|
|
46
|
+
flags?: string;
|
|
47
|
+
optionalIntentArguments?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
/**
|
|
39
51
|
* Verify whether the given argument is a
|
|
40
52
|
* valid class name.
|
|
@@ -1159,8 +1171,6 @@ export async function isAppRunning(this: ADB, pkg: string): Promise<boolean> {
|
|
|
1159
1171
|
return !_.isEmpty(await this.listAppProcessIds(pkg));
|
|
1160
1172
|
}
|
|
1161
1173
|
|
|
1162
|
-
// Private methods
|
|
1163
|
-
|
|
1164
1174
|
/**
|
|
1165
1175
|
* Parses optional intent arguments from a string.
|
|
1166
1176
|
*
|
|
@@ -1235,17 +1245,3 @@ function escapeShellArg(arg: string): string {
|
|
|
1235
1245
|
}
|
|
1236
1246
|
return arg.replace(/&/g, '\\&');
|
|
1237
1247
|
}
|
|
1238
|
-
|
|
1239
|
-
// Type definitions
|
|
1240
|
-
|
|
1241
|
-
export interface StartCmdOptions {
|
|
1242
|
-
user?: number | string;
|
|
1243
|
-
waitForLaunch?: boolean;
|
|
1244
|
-
pkg?: string;
|
|
1245
|
-
activity?: string;
|
|
1246
|
-
action?: string;
|
|
1247
|
-
category?: string;
|
|
1248
|
-
stopApp?: boolean;
|
|
1249
|
-
flags?: string;
|
|
1250
|
-
optionalIntentArguments?: string;
|
|
1251
|
-
}
|
|
@@ -2,7 +2,6 @@ import {log} from '../logger';
|
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import {retryInterval} from 'asyncbox';
|
|
4
4
|
import {util} from '@appium/support';
|
|
5
|
-
import B from 'bluebird';
|
|
6
5
|
import type {ADB} from '../adb';
|
|
7
6
|
import type {SetPropOpts} from './types';
|
|
8
7
|
import type {ExecError} from 'teen_process';
|
|
@@ -288,19 +287,6 @@ export async function toggleGPSLocationProvider(this: ADB, enabled: boolean): Pr
|
|
|
288
287
|
await this.shell(['cmd', 'location', 'set-location-enabled', enabled ? 'true' : 'false']);
|
|
289
288
|
}
|
|
290
289
|
|
|
291
|
-
/**
|
|
292
|
-
* Decorates an exception message with a solution link
|
|
293
|
-
*
|
|
294
|
-
* @param e The error object to be decorated
|
|
295
|
-
* @returns Either the same error or the decorated one
|
|
296
|
-
*/
|
|
297
|
-
function decorateWriteSecureSettingsException(e: Error): Error {
|
|
298
|
-
if (_.includes(e.message, 'requires:android.permission.WRITE_SECURE_SETTINGS')) {
|
|
299
|
-
e.message = `Check https://github.com/appium/appium/issues/13802 for throubleshooting. ${e.message}`;
|
|
300
|
-
}
|
|
301
|
-
return e;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
290
|
/**
|
|
305
291
|
* Set hidden api policy to manage access to non-SDK APIs.
|
|
306
292
|
* https://developer.android.com/preview/restrictions-non-sdk-interfaces
|
|
@@ -678,7 +664,7 @@ export async function isDataOn(this: ADB): Promise<boolean> {
|
|
|
678
664
|
*/
|
|
679
665
|
export async function isAnimationOn(this: ADB): Promise<boolean> {
|
|
680
666
|
return (
|
|
681
|
-
await
|
|
667
|
+
await Promise.all(
|
|
682
668
|
ANIMATION_SCALE_KEYS.map(async (k) => (await this.getSetting('global', k)) !== '0.0'),
|
|
683
669
|
)
|
|
684
670
|
).includes(true);
|
|
@@ -700,7 +686,7 @@ export async function isAnimationOn(this: ADB): Promise<boolean> {
|
|
|
700
686
|
* @throws If the adb setting command raises an exception.
|
|
701
687
|
*/
|
|
702
688
|
export async function setAnimationScale(this: ADB, value: number): Promise<void> {
|
|
703
|
-
await
|
|
689
|
+
await Promise.all(ANIMATION_SCALE_KEYS.map((k) => this.setSetting('global', k, value)));
|
|
704
690
|
}
|
|
705
691
|
|
|
706
692
|
/**
|
|
@@ -715,6 +701,19 @@ export async function getScreenOrientation(this: ADB): Promise<number | null> {
|
|
|
715
701
|
|
|
716
702
|
// #region Private functions
|
|
717
703
|
|
|
704
|
+
/**
|
|
705
|
+
* Decorates an exception message with a solution link
|
|
706
|
+
*
|
|
707
|
+
* @param e The error object to be decorated
|
|
708
|
+
* @returns Either the same error or the decorated one
|
|
709
|
+
*/
|
|
710
|
+
function decorateWriteSecureSettingsException(e: Error): Error {
|
|
711
|
+
if (_.includes(e.message, 'requires:android.permission.WRITE_SECURE_SETTINGS')) {
|
|
712
|
+
e.message = `Check https://github.com/appium/appium/issues/13802 for throubleshooting. ${e.message}`;
|
|
713
|
+
}
|
|
714
|
+
return e;
|
|
715
|
+
}
|
|
716
|
+
|
|
718
717
|
/**
|
|
719
718
|
* Reads SurfaceOrientation in dumpsys output
|
|
720
719
|
*
|
|
@@ -2,7 +2,6 @@ import {log} from '../logger';
|
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import net from 'node:net';
|
|
4
4
|
import {util, fs} from '@appium/support';
|
|
5
|
-
import B from 'bluebird';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
import * as ini from 'ini';
|
|
8
7
|
import type {ADB} from '../adb';
|
|
@@ -19,56 +18,6 @@ import type {
|
|
|
19
18
|
ExecTelnetOptions,
|
|
20
19
|
} from './types';
|
|
21
20
|
|
|
22
|
-
/**
|
|
23
|
-
* Retrieves the list of available Android emulators
|
|
24
|
-
*
|
|
25
|
-
* @returns
|
|
26
|
-
*/
|
|
27
|
-
async function listEmulators(): Promise<EmuInfo[]> {
|
|
28
|
-
let avdsRoot = process.env.ANDROID_AVD_HOME;
|
|
29
|
-
if (await dirExists(avdsRoot ?? '')) {
|
|
30
|
-
return await getAvdConfigPaths(avdsRoot as string);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (avdsRoot) {
|
|
34
|
-
log.warn(
|
|
35
|
-
`The value of the ANDROID_AVD_HOME environment variable '${avdsRoot}' is not an existing directory`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const prefsRoot = await getAndroidPrefsRoot();
|
|
40
|
-
if (!prefsRoot) {
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
avdsRoot = path.resolve(prefsRoot, 'avd');
|
|
45
|
-
if (!(await dirExists(avdsRoot))) {
|
|
46
|
-
log.debug(`Virtual devices config root '${avdsRoot}' is not an existing directory`);
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return await getAvdConfigPaths(avdsRoot);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get configuration paths of all virtual devices
|
|
55
|
-
*
|
|
56
|
-
* @param avdsRoot Path to the directory that contains the AVD .ini files
|
|
57
|
-
* @returns
|
|
58
|
-
*/
|
|
59
|
-
async function getAvdConfigPaths(avdsRoot: string): Promise<EmuInfo[]> {
|
|
60
|
-
const configs = await fs.glob('*.ini', {
|
|
61
|
-
cwd: avdsRoot,
|
|
62
|
-
absolute: true,
|
|
63
|
-
});
|
|
64
|
-
return configs
|
|
65
|
-
.map((confPath) => {
|
|
66
|
-
const avdName = path.basename(confPath).split('.').slice(0, -1).join('.');
|
|
67
|
-
return {name: avdName, config: confPath};
|
|
68
|
-
})
|
|
69
|
-
.filter(({name}) => _.trim(name));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
21
|
/**
|
|
73
22
|
* Check the emulator state.
|
|
74
23
|
*
|
|
@@ -309,7 +258,7 @@ export async function execEmuConsoleCommand(
|
|
|
309
258
|
port,
|
|
310
259
|
});
|
|
311
260
|
|
|
312
|
-
return await new
|
|
261
|
+
return await new Promise<string>((resolve, reject) => {
|
|
313
262
|
const connTimeoutObj = setTimeout(
|
|
314
263
|
() =>
|
|
315
264
|
reject(
|
|
@@ -480,6 +429,56 @@ export async function sendTelnetCommand(this: ADB, command: string): Promise<str
|
|
|
480
429
|
|
|
481
430
|
// #region Private functions
|
|
482
431
|
|
|
432
|
+
/**
|
|
433
|
+
* Retrieves the list of available Android emulators
|
|
434
|
+
*
|
|
435
|
+
* @returns
|
|
436
|
+
*/
|
|
437
|
+
async function listEmulators(): Promise<EmuInfo[]> {
|
|
438
|
+
let avdsRoot = process.env.ANDROID_AVD_HOME;
|
|
439
|
+
if (await dirExists(avdsRoot ?? '')) {
|
|
440
|
+
return await getAvdConfigPaths(avdsRoot as string);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (avdsRoot) {
|
|
444
|
+
log.warn(
|
|
445
|
+
`The value of the ANDROID_AVD_HOME environment variable '${avdsRoot}' is not an existing directory`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const prefsRoot = await getAndroidPrefsRoot();
|
|
450
|
+
if (!prefsRoot) {
|
|
451
|
+
return [];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
avdsRoot = path.resolve(prefsRoot, 'avd');
|
|
455
|
+
if (!(await dirExists(avdsRoot))) {
|
|
456
|
+
log.debug(`Virtual devices config root '${avdsRoot}' is not an existing directory`);
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return await getAvdConfigPaths(avdsRoot);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get configuration paths of all virtual devices
|
|
465
|
+
*
|
|
466
|
+
* @param avdsRoot Path to the directory that contains the AVD .ini files
|
|
467
|
+
* @returns
|
|
468
|
+
*/
|
|
469
|
+
async function getAvdConfigPaths(avdsRoot: string): Promise<EmuInfo[]> {
|
|
470
|
+
const configs = await fs.glob('*.ini', {
|
|
471
|
+
cwd: avdsRoot,
|
|
472
|
+
absolute: true,
|
|
473
|
+
});
|
|
474
|
+
return configs
|
|
475
|
+
.map((confPath) => {
|
|
476
|
+
const avdName = path.basename(confPath).split('.').slice(0, -1).join('.');
|
|
477
|
+
return {name: avdName, config: confPath};
|
|
478
|
+
})
|
|
479
|
+
.filter(({name}) => _.trim(name));
|
|
480
|
+
}
|
|
481
|
+
|
|
483
482
|
/**
|
|
484
483
|
* Retrieves the full path to the Android preferences root
|
|
485
484
|
*
|
package/lib/tools/fs-commands.ts
CHANGED
|
@@ -3,10 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import type {ADB} from '../adb';
|
|
4
4
|
import type {TeenProcessExecOptions} from 'teen_process';
|
|
5
5
|
|
|
6
|
-
function shellEscapeSingleQuotes(str: string): string {
|
|
7
|
-
return str.replace(/'/g, "'\\''");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
6
|
/**
|
|
11
7
|
* Verify whether a remote path exists on the device under test.
|
|
12
8
|
*
|
|
@@ -132,3 +128,7 @@ export async function pull(
|
|
|
132
128
|
export async function mkdir(this: ADB, remotePath: string): Promise<string> {
|
|
133
129
|
return await this.shell([`mkdir -p -- '${shellEscapeSingleQuotes(remotePath)}'`]);
|
|
134
130
|
}
|
|
131
|
+
|
|
132
|
+
function shellEscapeSingleQuotes(str: string): string {
|
|
133
|
+
return str.replace(/'/g, "'\\''");
|
|
134
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {log} from '../logger';
|
|
2
|
-
import {waitForCondition} from 'asyncbox';
|
|
3
|
-
import B from 'bluebird';
|
|
2
|
+
import {sleep, waitForCondition} from 'asyncbox';
|
|
4
3
|
import type {ADB} from '../adb';
|
|
5
4
|
import type {KeyboardState} from './types';
|
|
6
5
|
|
|
@@ -194,7 +193,7 @@ export async function runInImeContext<T>(this: ADB, ime: string, fn: () => Promi
|
|
|
194
193
|
await cycleImeState(originalIme);
|
|
195
194
|
}
|
|
196
195
|
// https://github.com/appium/appium/issues/15943
|
|
197
|
-
await
|
|
196
|
+
await sleep(500);
|
|
198
197
|
}
|
|
199
198
|
try {
|
|
200
199
|
return await fn();
|
package/lib/tools/lockmgmt.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {log} from '../logger';
|
|
2
2
|
import _ from 'lodash';
|
|
3
|
-
import
|
|
4
|
-
import {waitForCondition} from 'asyncbox';
|
|
3
|
+
import {sleep, waitForCondition} from 'asyncbox';
|
|
5
4
|
import type {ADB} from '../adb';
|
|
6
5
|
|
|
7
6
|
const CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR = `Credential can't be null or empty`;
|
|
@@ -11,51 +10,6 @@ const KEYCODE_POWER = 26;
|
|
|
11
10
|
const KEYCODE_WAKEUP = 224; // works over API Level 20
|
|
12
11
|
const HIDE_KEYBOARD_WAIT_TIME = 100;
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* @param verb
|
|
16
|
-
* @param oldCredential
|
|
17
|
-
* @param args
|
|
18
|
-
*/
|
|
19
|
-
function buildCommand(
|
|
20
|
-
verb: string,
|
|
21
|
-
oldCredential: string | null = null,
|
|
22
|
-
...args: string[]
|
|
23
|
-
): string[] {
|
|
24
|
-
const cmd = ['locksettings', verb];
|
|
25
|
-
if (oldCredential && !_.isEmpty(oldCredential)) {
|
|
26
|
-
cmd.push('--old', oldCredential);
|
|
27
|
-
}
|
|
28
|
-
if (!_.isEmpty(args)) {
|
|
29
|
-
cmd.push(...args);
|
|
30
|
-
}
|
|
31
|
-
return cmd;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Performs swipe up gesture on the screen
|
|
36
|
-
*
|
|
37
|
-
* @param windowDumpsys The output of `adb shell dumpsys window` command
|
|
38
|
-
* @throws {Error} If the display size cannot be retrieved
|
|
39
|
-
*/
|
|
40
|
-
async function swipeUp(this: ADB, windowDumpsys: string): Promise<void> {
|
|
41
|
-
const dimensionsMatch = /init=(\d+)x(\d+)/.exec(windowDumpsys);
|
|
42
|
-
if (!dimensionsMatch) {
|
|
43
|
-
throw new Error('Cannot retrieve the display size');
|
|
44
|
-
}
|
|
45
|
-
const displayWidth = parseInt(dimensionsMatch[1], 10);
|
|
46
|
-
const displayHeight = parseInt(dimensionsMatch[2], 10);
|
|
47
|
-
const x0 = displayWidth / 2;
|
|
48
|
-
const y0 = (displayHeight / 5) * 4;
|
|
49
|
-
const x1 = x0;
|
|
50
|
-
const y1 = displayHeight / 5;
|
|
51
|
-
await this.shell([
|
|
52
|
-
'input',
|
|
53
|
-
'touchscreen',
|
|
54
|
-
'swipe',
|
|
55
|
-
...[x0, y0, x1, y1].map((c) => `${Math.trunc(c)}`),
|
|
56
|
-
]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
13
|
/**
|
|
60
14
|
* Check whether the device supports lock settings management with `locksettings`
|
|
61
15
|
* command line tool. This tool has been added to Android toolset since API 27 Oreo
|
|
@@ -249,7 +203,7 @@ export async function setLockCredential(
|
|
|
249
203
|
* @return True if the device is locked.
|
|
250
204
|
*/
|
|
251
205
|
export async function isScreenLocked(this: ADB): Promise<boolean> {
|
|
252
|
-
const [windowOutput, powerOutput] = await
|
|
206
|
+
const [windowOutput, powerOutput] = await Promise.all([
|
|
253
207
|
this.shell(['dumpsys', 'window']),
|
|
254
208
|
this.shell(['dumpsys', 'power']),
|
|
255
209
|
]);
|
|
@@ -284,7 +238,7 @@ export async function dismissKeyguard(this: ADB): Promise<void> {
|
|
|
284
238
|
|
|
285
239
|
log.debug('Swiping up to dismiss the keyguard');
|
|
286
240
|
if (await this.hideKeyboard()) {
|
|
287
|
-
await
|
|
241
|
+
await sleep(HIDE_KEYBOARD_WAIT_TIME);
|
|
288
242
|
}
|
|
289
243
|
log.debug('Dismissing notifications from the unlock view');
|
|
290
244
|
await this.shell(['service', 'call', 'notification', '1']);
|
|
@@ -323,8 +277,90 @@ export async function lock(this: ADB): Promise<void> {
|
|
|
323
277
|
}
|
|
324
278
|
}
|
|
325
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Checks mShowingLockscreen or mDreamingLockscreen in dumpsys output to determine
|
|
282
|
+
* if lock screen is showing
|
|
283
|
+
*
|
|
284
|
+
* A note: `adb shell dumpsys trust` performs better while detecting the locked screen state
|
|
285
|
+
* in comparison to `adb dumpsys window` output parsing.
|
|
286
|
+
* But the trust command does not work for `Swipe` unlock pattern.
|
|
287
|
+
*
|
|
288
|
+
* In some Android devices (Probably around Android 10+), `mShowingLockscreen` and `mDreamingLockscreen`
|
|
289
|
+
* do not work to detect lock status. Instead, keyguard preferences helps to detect the lock condition.
|
|
290
|
+
* Some devices such as Android TV do not have keyguard, so we should keep
|
|
291
|
+
* screen condition as this primary method.
|
|
292
|
+
*
|
|
293
|
+
* @param dumpsys - The output of dumpsys window command.
|
|
294
|
+
* @return True if lock screen is showing.
|
|
295
|
+
*/
|
|
296
|
+
export function isShowingLockscreen(dumpsys: string): boolean {
|
|
297
|
+
return (
|
|
298
|
+
_.some(['mShowingLockscreen=true', 'mDreamingLockscreen=true'], (x) => dumpsys.includes(x)) ||
|
|
299
|
+
// `mIsShowing` and `mInputRestricted` are `true` in lock condition. `false` is unlock condition.
|
|
300
|
+
_.every([/KeyguardStateMonitor[\n\s]+mIsShowing=true/, /\s+mInputRestricted=true/], (x) =>
|
|
301
|
+
x.test(dumpsys),
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Checks screenState has SCREEN_STATE_OFF in dumpsys output to determine
|
|
308
|
+
* possible lock screen.
|
|
309
|
+
*
|
|
310
|
+
* @param dumpsys - The output of dumpsys window command.
|
|
311
|
+
* @return True if lock screen is showing.
|
|
312
|
+
*/
|
|
313
|
+
export function isScreenStateOff(dumpsys: string): boolean {
|
|
314
|
+
return /\s+screenState=SCREEN_STATE_OFF/i.test(dumpsys);
|
|
315
|
+
}
|
|
316
|
+
|
|
326
317
|
// #region Private functions
|
|
327
318
|
|
|
319
|
+
/**
|
|
320
|
+
* @param verb
|
|
321
|
+
* @param oldCredential
|
|
322
|
+
* @param args
|
|
323
|
+
*/
|
|
324
|
+
function buildCommand(
|
|
325
|
+
verb: string,
|
|
326
|
+
oldCredential: string | null = null,
|
|
327
|
+
...args: string[]
|
|
328
|
+
): string[] {
|
|
329
|
+
const cmd = ['locksettings', verb];
|
|
330
|
+
if (oldCredential && !_.isEmpty(oldCredential)) {
|
|
331
|
+
cmd.push('--old', oldCredential);
|
|
332
|
+
}
|
|
333
|
+
if (!_.isEmpty(args)) {
|
|
334
|
+
cmd.push(...args);
|
|
335
|
+
}
|
|
336
|
+
return cmd;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Performs swipe up gesture on the screen
|
|
341
|
+
*
|
|
342
|
+
* @param windowDumpsys The output of `adb shell dumpsys window` command
|
|
343
|
+
* @throws {Error} If the display size cannot be retrieved
|
|
344
|
+
*/
|
|
345
|
+
async function swipeUp(this: ADB, windowDumpsys: string): Promise<void> {
|
|
346
|
+
const dimensionsMatch = /init=(\d+)x(\d+)/.exec(windowDumpsys);
|
|
347
|
+
if (!dimensionsMatch) {
|
|
348
|
+
throw new Error('Cannot retrieve the display size');
|
|
349
|
+
}
|
|
350
|
+
const displayWidth = parseInt(dimensionsMatch[1], 10);
|
|
351
|
+
const displayHeight = parseInt(dimensionsMatch[2], 10);
|
|
352
|
+
const x0 = displayWidth / 2;
|
|
353
|
+
const y0 = (displayHeight / 5) * 4;
|
|
354
|
+
const x1 = x0;
|
|
355
|
+
const y1 = displayHeight / 5;
|
|
356
|
+
await this.shell([
|
|
357
|
+
'input',
|
|
358
|
+
'touchscreen',
|
|
359
|
+
'swipe',
|
|
360
|
+
...[x0, y0, x1, y1].map((c) => `${Math.trunc(c)}`),
|
|
361
|
+
]);
|
|
362
|
+
}
|
|
363
|
+
|
|
328
364
|
/**
|
|
329
365
|
* Checks mScreenOnFully in dumpsys output to determine if screen is showing
|
|
330
366
|
* Default is true.
|
|
@@ -365,41 +401,4 @@ function isInDozingMode(dumpsys: string): boolean {
|
|
|
365
401
|
return /^[\s\w]+wakefulness[^=]*=Dozing$/im.test(dumpsys);
|
|
366
402
|
}
|
|
367
403
|
|
|
368
|
-
/**
|
|
369
|
-
* Checks mShowingLockscreen or mDreamingLockscreen in dumpsys output to determine
|
|
370
|
-
* if lock screen is showing
|
|
371
|
-
*
|
|
372
|
-
* A note: `adb shell dumpsys trust` performs better while detecting the locked screen state
|
|
373
|
-
* in comparison to `adb dumpsys window` output parsing.
|
|
374
|
-
* But the trust command does not work for `Swipe` unlock pattern.
|
|
375
|
-
*
|
|
376
|
-
* In some Android devices (Probably around Android 10+), `mShowingLockscreen` and `mDreamingLockscreen`
|
|
377
|
-
* do not work to detect lock status. Instead, keyguard preferences helps to detect the lock condition.
|
|
378
|
-
* Some devices such as Android TV do not have keyguard, so we should keep
|
|
379
|
-
* screen condition as this primary method.
|
|
380
|
-
*
|
|
381
|
-
* @param dumpsys - The output of dumpsys window command.
|
|
382
|
-
* @return True if lock screen is showing.
|
|
383
|
-
*/
|
|
384
|
-
export function isShowingLockscreen(dumpsys: string): boolean {
|
|
385
|
-
return (
|
|
386
|
-
_.some(['mShowingLockscreen=true', 'mDreamingLockscreen=true'], (x) => dumpsys.includes(x)) ||
|
|
387
|
-
// `mIsShowing` and `mInputRestricted` are `true` in lock condition. `false` is unlock condition.
|
|
388
|
-
_.every([/KeyguardStateMonitor[\n\s]+mIsShowing=true/, /\s+mInputRestricted=true/], (x) =>
|
|
389
|
-
x.test(dumpsys),
|
|
390
|
-
)
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Checks screenState has SCREEN_STATE_OFF in dumpsys output to determine
|
|
396
|
-
* possible lock screen.
|
|
397
|
-
*
|
|
398
|
-
* @param dumpsys - The output of dumpsys window command.
|
|
399
|
-
* @return True if lock screen is showing.
|
|
400
|
-
*/
|
|
401
|
-
export function isScreenStateOff(dumpsys: string): boolean {
|
|
402
|
-
return /\s+screenState=SCREEN_STATE_OFF/i.test(dumpsys);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
404
|
// #endregion
|