appium-adb 14.0.4 → 14.0.6

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.
@@ -3,9 +3,21 @@ import { fs, tempDir, util, system } from '@appium/support';
3
3
  import { log } from '../logger.js';
4
4
  import { waitForCondition } from 'asyncbox';
5
5
  import path from 'node:path';
6
-
7
- /** @type {import('./types').StringRecord<import('./types').InstallState>} */
8
- export const APP_INSTALL_STATE = {
6
+ import type { ADB } from '../adb.js';
7
+ import type { ExecError } from 'teen_process';
8
+ import type {
9
+ StringRecord,
10
+ InstallState,
11
+ ResolveActivityOptions,
12
+ IsAppInstalledOptions,
13
+ StartUriOptions,
14
+ StartAppOptions,
15
+ AppInfo,
16
+ PackageActivityInfo,
17
+ } from './types.js';
18
+
19
+ // Constants
20
+ export const APP_INSTALL_STATE: StringRecord<InstallState> = {
9
21
  UNKNOWN: 'unknown',
10
22
  NOT_INSTALLED: 'notInstalled',
11
23
  NEWER_VERSION_INSTALLED: 'newerVersionInstalled',
@@ -22,17 +34,17 @@ const RESOLVER_ACTIVITY_NAME = 'android/com.android.internal.app.ResolverActivit
22
34
  const MAIN_ACTION = 'android.intent.action.MAIN';
23
35
  const LAUNCHER_CATEGORY = 'android.intent.category.LAUNCHER';
24
36
 
37
+ // Public methods
25
38
 
26
39
  /**
27
40
  * Verify whether the given argument is a
28
41
  * valid class name.
29
42
  *
30
- * @this {import('../adb.js').ADB}
31
- * @param {string} classString - The actual class name to be verified.
32
- * @return {boolean} The result of Regexp.exec operation
43
+ * @param classString - The actual class name to be verified.
44
+ * @returns The result of Regexp.exec operation
33
45
  * or _null_ if no matches are found.
34
46
  */
35
- export function isValidClass (classString) {
47
+ export function isValidClass (this: ADB, classString: string): boolean {
36
48
  // some.package/some.package.Activity
37
49
  return !!matchComponentName(classString);
38
50
  }
@@ -42,13 +54,12 @@ export function isValidClass (classString) {
42
54
  * given package. It is expected the package is already installed on
43
55
  * the device under test.
44
56
  *
45
- * @this {import('../adb.js').ADB}
46
- * @param {string} pkg - The target package identifier
47
- * @param {import('./types').ResolveActivityOptions} opts
48
- * @return {Promise<string>} Fully qualified name of the launchable activity
57
+ * @param pkg - The target package identifier
58
+ * @param opts - Options for resolving the activity
59
+ * @returns Fully qualified name of the launchable activity
49
60
  * @throws {Error} If there was an error while resolving the activity name
50
61
  */
51
- export async function resolveLaunchableActivity (pkg, opts = {}) {
62
+ export async function resolveLaunchableActivity (this: ADB, pkg: string, opts: ResolveActivityOptions = {}): Promise<string> {
52
63
  const { preferCmd = true } = opts;
53
64
  if (!preferCmd || await this.getApiLevel() < 24) {
54
65
  const stdout = await this.shell(['dumpsys', 'package', pkg]);
@@ -65,9 +76,9 @@ export async function resolveLaunchableActivity (pkg, opts = {}) {
65
76
  try {
66
77
  const tmpApp = await this.pullApk(pkg, tmpRoot);
67
78
  const {apkActivity} = await this.packageAndLaunchActivityFromManifest(tmpApp);
68
- return /** @type {string} */ (apkActivity);
79
+ return apkActivity as string;
69
80
  } catch (e) {
70
- const err = /** @type {Error} */ (e);
81
+ const err = e as Error;
71
82
  log.debug(err.stack);
72
83
  log.warn(`Unable to resolve the launchable activity of '${pkg}'. ` +
73
84
  `The very first match of the dumpsys output is going to be used. ` +
@@ -92,33 +103,31 @@ export async function resolveLaunchableActivity (pkg, opts = {}) {
92
103
 
93
104
  /**
94
105
  * Forcefully stops the app and puts it in the "stopped" state.
95
- * Android treats a stopped app as if it was never launched since boot:
106
+ * Android treats a "stopped" app as if it was never launched since boot:
96
107
  * - It cannot receive broadcast intents (except for explicit ones).
97
108
  * - Scheduled jobs, alarms, and services are cancelled.
98
- * - The app wont restart until the user explicitly launches it again.
99
- * Its the same as when a user swipes an app away from Settings → Apps → Force Stop.
109
+ * - The app won't restart until the user explicitly launches it again.
110
+ * It's the same as when a user swipes an app away from Settings → Apps → Force Stop.
100
111
  *
101
- * @this {import('../adb.js').ADB}
102
- * @param {string} pkg - The package name to be stopped.
103
- * @return {Promise<string>} The output of the corresponding adb command.
112
+ * @param pkg - The package name to be stopped.
113
+ * @returns The output of the corresponding adb command.
104
114
  */
105
- export async function forceStop (pkg) {
115
+ export async function forceStop (this: ADB, pkg: string): Promise<string> {
106
116
  return await this.shell(['am', 'force-stop', pkg]);
107
117
  }
108
118
 
109
119
  /**
110
- * Gracefully kills the apps process, similar to how Android would do it
120
+ * Gracefully kills the app's process, similar to how Android would do it
111
121
  * automatically when low on memory.
112
- * It only kills the process, without changing the apps stopped state.
122
+ * It only kills the process, without changing the app's "stopped" state.
113
123
  * Background services or broadcast receivers may restart soon after,
114
124
  * if they are still scheduled or registered.
115
125
  * No data or state (like alarms, jobs, etc.) are cleared.
116
126
  *
117
- * @this {import('../adb.js').ADB}
118
- * @param {string} pkg - The package name to be stopped.
119
- * @return {Promise<string>} The output of the corresponding adb command.
127
+ * @param pkg - The package name to be stopped.
128
+ * @returns The output of the corresponding adb command.
120
129
  */
121
- export async function killPackage (pkg) {
130
+ export async function killPackage (this: ADB, pkg: string): Promise<string> {
122
131
  return await this.shell(['am', 'kill', pkg]);
123
132
  }
124
133
 
@@ -126,11 +135,10 @@ export async function killPackage (pkg) {
126
135
  * Clear the user data of the particular application on the device
127
136
  * under test.
128
137
  *
129
- * @this {import('../adb.js').ADB}
130
- * @param {string} pkg - The package name to be cleared.
131
- * @return {Promise<string>} The output of the corresponding adb command.
138
+ * @param pkg - The package name to be cleared.
139
+ * @returns The output of the corresponding adb command.
132
140
  */
133
- export async function clear (pkg) {
141
+ export async function clear (this: ADB, pkg: string): Promise<string> {
134
142
  return await this.shell(['pm', 'clear', pkg]);
135
143
  }
136
144
 
@@ -139,15 +147,14 @@ export async function clear (pkg) {
139
147
  * This method is only useful on Android 6.0+ and for applications
140
148
  * that support components-based permissions setting.
141
149
  *
142
- * @this {import('../adb.js').ADB}
143
- * @param {string} pkg - The package name to be processed.
144
- * @param {string} [apk] - The path to the actual apk file.
150
+ * @param pkg - The package name to be processed.
151
+ * @param apk - The path to the actual apk file.
145
152
  * @throws {Error} If there was an error while granting permissions
146
153
  */
147
- export async function grantAllPermissions (pkg, apk) {
154
+ export async function grantAllPermissions (this: ADB, pkg: string, apk?: string): Promise<void> {
148
155
  const apiLevel = await this.getApiLevel();
149
156
  let targetSdk = 0;
150
- let dumpsysOutput = null;
157
+ let dumpsysOutput: string | null = null;
151
158
  try {
152
159
  if (!apk) {
153
160
  /**
@@ -193,12 +200,11 @@ export async function grantAllPermissions (pkg, apk) {
193
200
  * This call is more performant than `grantPermission` one, since it combines
194
201
  * multiple `adb shell` calls into a single command.
195
202
  *
196
- * @this {import('../adb.js').ADB}
197
- * @param {string} pkg - The package name to be processed.
198
- * @param {Array<string>} permissions - The list of permissions to be granted.
203
+ * @param pkg - The package name to be processed.
204
+ * @param permissions - The list of permissions to be granted.
199
205
  * @throws {Error} If there was an error while changing permissions.
200
206
  */
201
- export async function grantPermissions (pkg, permissions) {
207
+ export async function grantPermissions (this: ADB, pkg: string, permissions: string[]): Promise<void> {
202
208
  // As it consumes more time for granting each permission,
203
209
  // trying to grant all permission by forming equivalent command.
204
210
  // Also, it is necessary to split long commands into chunks, since the maximum length of
@@ -207,7 +213,7 @@ export async function grantPermissions (pkg, permissions) {
207
213
  try {
208
214
  await this.shellChunks((perm) => ['pm', 'grant', pkg, perm], permissions);
209
215
  } catch (e) {
210
- const err = /** @type {import('teen_process').ExecError} */(e);
216
+ const err = e as ExecError;
211
217
  if (!IGNORED_PERM_ERRORS.some((pattern) => pattern.test(err.stderr || err.message))) {
212
218
  throw err;
213
219
  }
@@ -217,16 +223,15 @@ export async function grantPermissions (pkg, permissions) {
217
223
  /**
218
224
  * Grant single permission for the particular package.
219
225
  *
220
- * @this {import('../adb.js').ADB}
221
- * @param {string} pkg - The package name to be processed.
222
- * @param {string} permission - The full name of the permission to be granted.
226
+ * @param pkg - The package name to be processed.
227
+ * @param permission - The full name of the permission to be granted.
223
228
  * @throws {Error} If there was an error while changing permissions.
224
229
  */
225
- export async function grantPermission (pkg, permission) {
230
+ export async function grantPermission (this: ADB, pkg: string, permission: string): Promise<void> {
226
231
  try {
227
232
  await this.shell(['pm', 'grant', pkg, permission]);
228
233
  } catch (e) {
229
- const err = /** @type {import('teen_process').ExecError} */(e);
234
+ const err = e as ExecError;
230
235
  if (!NOT_CHANGEABLE_PERM_ERROR.test(err.stderr || err.message)) {
231
236
  throw err;
232
237
  }
@@ -236,16 +241,15 @@ export async function grantPermission (pkg, permission) {
236
241
  /**
237
242
  * Revoke single permission from the particular package.
238
243
  *
239
- * @this {import('../adb.js').ADB}
240
- * @param {string} pkg - The package name to be processed.
241
- * @param {string} permission - The full name of the permission to be revoked.
244
+ * @param pkg - The package name to be processed.
245
+ * @param permission - The full name of the permission to be revoked.
242
246
  * @throws {Error} If there was an error while changing permissions.
243
247
  */
244
- export async function revokePermission (pkg, permission) {
248
+ export async function revokePermission (this: ADB, pkg: string, permission: string): Promise<void> {
245
249
  try {
246
250
  await this.shell(['pm', 'revoke', pkg, permission]);
247
251
  } catch (e) {
248
- const err = /** @type {import('teen_process').ExecError} */(e);
252
+ const err = e as ExecError;
249
253
  if (!NOT_CHANGEABLE_PERM_ERROR.test(err.stderr || err.message)) {
250
254
  throw err;
251
255
  }
@@ -255,14 +259,13 @@ export async function revokePermission (pkg, permission) {
255
259
  /**
256
260
  * Retrieve the list of granted permissions for the particular package.
257
261
  *
258
- * @this {import('../adb.js').ADB}
259
- * @param {string} pkg - The package name to be processed.
260
- * @param {string?} [cmdOutput=null] - Optional parameter containing command output of
262
+ * @param pkg - The package name to be processed.
263
+ * @param cmdOutput - Optional parameter containing command output of
261
264
  * _dumpsys package_ command. It may speed up the method execution.
262
- * @return {Promise<string[]>} The list of granted permissions or an empty list.
265
+ * @returns The list of granted permissions or an empty list.
263
266
  * @throws {Error} If there was an error while changing permissions.
264
267
  */
265
- export async function getGrantedPermissions (pkg, cmdOutput = null) {
268
+ export async function getGrantedPermissions (this: ADB, pkg: string, cmdOutput: string | null = null): Promise<string[]> {
266
269
  log.debug('Retrieving granted permissions');
267
270
  const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]);
268
271
  return extractMatchingPermissions(stdout, ['install', 'runtime'], true);
@@ -271,13 +274,12 @@ export async function getGrantedPermissions (pkg, cmdOutput = null) {
271
274
  /**
272
275
  * Retrieve the list of denied permissions for the particular package.
273
276
  *
274
- * @this {import('../adb.js').ADB}
275
- * @param {string} pkg - The package name to be processed.
276
- * @param {string?} [cmdOutput=null] - Optional parameter containing command output of
277
+ * @param pkg - The package name to be processed.
278
+ * @param cmdOutput - Optional parameter containing command output of
277
279
  * _dumpsys package_ command. It may speed up the method execution.
278
- * @return {Promise<string[]>} The list of denied permissions or an empty list.
280
+ * @returns The list of denied permissions or an empty list.
279
281
  */
280
- export async function getDeniedPermissions (pkg, cmdOutput = null) {
282
+ export async function getDeniedPermissions (this: ADB, pkg: string, cmdOutput: string | null = null): Promise<string[]> {
281
283
  log.debug('Retrieving denied permissions');
282
284
  const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]);
283
285
  return extractMatchingPermissions(stdout, ['install', 'runtime'], false);
@@ -286,13 +288,12 @@ export async function getDeniedPermissions (pkg, cmdOutput = null) {
286
288
  /**
287
289
  * Retrieve the list of requested permissions for the particular package.
288
290
  *
289
- * @this {import('../adb.js').ADB}
290
- * @param {string} pkg - The package name to be processed.
291
- * @param {string?} [cmdOutput=null] - Optional parameter containing command output of
291
+ * @param pkg - The package name to be processed.
292
+ * @param cmdOutput - Optional parameter containing command output of
292
293
  * _dumpsys package_ command. It may speed up the method execution.
293
- * @return {Promise<string[]>} The list of requested permissions or an empty list.
294
+ * @returns The list of requested permissions or an empty list.
294
295
  */
295
- export async function getReqPermissions (pkg, cmdOutput = null) {
296
+ export async function getReqPermissions (this: ADB, pkg: string, cmdOutput: string | null = null): Promise<string[]> {
296
297
  log.debug('Retrieving requested permissions');
297
298
  const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]);
298
299
  return extractMatchingPermissions(stdout, ['requested']);
@@ -301,15 +302,14 @@ export async function getReqPermissions (pkg, cmdOutput = null) {
301
302
  /**
302
303
  * Stop the particular package if it is running and clears its application data.
303
304
  *
304
- * @this {import('../adb.js').ADB}
305
- * @param {string} pkg - The package name to be processed.
305
+ * @param pkg - The package name to be processed.
306
306
  */
307
- export async function stopAndClear (pkg) {
307
+ export async function stopAndClear (this: ADB, pkg: string): Promise<void> {
308
308
  try {
309
309
  await this.forceStop(pkg);
310
310
  await this.clear(pkg);
311
311
  } catch (e) {
312
- const err = /** @type {Error} */ (e);
312
+ const err = e as Error;
313
313
  throw new Error(`Cannot stop and clear ${pkg}. Original error: ${err.message}`);
314
314
  }
315
315
  }
@@ -317,19 +317,19 @@ export async function stopAndClear (pkg) {
317
317
  /**
318
318
  * Get the package info from the installed application.
319
319
  *
320
- * @this {import('../adb.js').ADB}
321
- * @param {string} pkg - The name of the installed package.
322
- * @return {Promise<import('./types').AppInfo>} The parsed application information.
320
+ * @param pkg - The name of the installed package.
321
+ * @returns The parsed application information.
323
322
  */
324
- export async function getPackageInfo (pkg) {
323
+ export async function getPackageInfo (this: ADB, pkg: string): Promise<AppInfo> {
325
324
  log.debug(`Getting package info for '${pkg}'`);
326
- const result = {name: pkg};
327
- let stdout;
325
+ const result: AppInfo = {name: pkg};
326
+ let stdout: string;
328
327
  try {
329
328
  stdout = await this.shell(['dumpsys', 'package', pkg]);
330
329
  } catch (err) {
331
- log.debug(err.stack);
332
- log.warn(`Got an unexpected error while dumping package info: ${err.message}`);
330
+ const error = err as Error;
331
+ log.debug(error.stack);
332
+ log.warn(`Got an unexpected error while dumping package info: ${error.message}`);
333
333
  return result;
334
334
  }
335
335
 
@@ -353,13 +353,12 @@ export async function getPackageInfo (pkg) {
353
353
  /**
354
354
  * Fetches base.apk of the given package to the local file system
355
355
  *
356
- * @this {import('../adb.js').ADB}
357
- * @param {string} pkg The package identifier (must be already installed on the device)
358
- * @param {string} tmpDir The destination folder path
359
- * @returns {Promise<string>} Full path to the downloaded file
356
+ * @param pkg - The package identifier (must be already installed on the device)
357
+ * @param tmpDir - The destination folder path
358
+ * @returns Full path to the downloaded file
360
359
  * @throws {Error} If there was an error while fetching the .apk
361
360
  */
362
- export async function pullApk (pkg, tmpDir) {
361
+ export async function pullApk (this: ADB, pkg: string, tmpDir: string): Promise<string> {
363
362
  const stdout = _.trim(await this.shell(['pm', 'path', pkg]));
364
363
  const packageMarker = 'package:';
365
364
  if (!_.startsWith(stdout, packageMarker)) {
@@ -378,11 +377,10 @@ export async function pullApk (pkg, tmpDir) {
378
377
  * The action literally simulates
379
378
  * clicking the corresponding application icon on the dashboard.
380
379
  *
381
- * @this {import('../adb.js').ADB}
382
- * @param {string} appId - Application package identifier
380
+ * @param appId - Application package identifier
383
381
  * @throws {Error} If the app cannot be activated
384
382
  */
385
- export async function activateApp (appId) {
383
+ export async function activateApp (this: ADB, appId: string): Promise<void> {
386
384
  log.debug(`Activating '${appId}'`);
387
385
  const apiLevel = await this.getApiLevel();
388
386
  // Fallback to Monkey in older APIs
@@ -398,7 +396,8 @@ export async function activateApp (appId) {
398
396
  output = await this.shell(cmd);
399
397
  log.debug(`Command stdout: ${output}`);
400
398
  } catch (e) {
401
- throw log.errorWithException(`Cannot activate '${appId}'. Original error: ${e.message}`);
399
+ const error = e as Error;
400
+ throw log.errorWithException(`Cannot activate '${appId}'. Original error: ${error.message}`);
402
401
  }
403
402
  if (output.includes('monkey aborted')) {
404
403
  throw log.errorWithException(`Cannot activate '${appId}'. Are you sure it is installed?`);
@@ -435,19 +434,17 @@ export async function activateApp (appId) {
435
434
  /**
436
435
  * Check whether the particular package is present on the device under test.
437
436
  *
438
- * @this {import('../adb.js').ADB}
439
- * @param {string} pkg - The name of the package to check.
440
- * @param {import('./types').IsAppInstalledOptions} [opts={}]
441
- * @return {Promise<boolean>} True if the package is installed.
437
+ * @param pkg - The name of the package to check.
438
+ * @param opts - Options for checking installation
439
+ * @returns True if the package is installed.
442
440
  */
443
- export async function isAppInstalled (pkg, opts = {}) {
441
+ export async function isAppInstalled (this: ADB, pkg: string, opts: IsAppInstalledOptions = {}): Promise<boolean> {
444
442
  const {
445
443
  user,
446
444
  } = opts;
447
445
 
448
446
  log.debug(`Getting install status for ${pkg}`);
449
- /** @type {boolean} */
450
- let isInstalled;
447
+ let isInstalled: boolean;
451
448
  if (await this.getApiLevel() < 26) {
452
449
  try {
453
450
  const cmd = ['pm', 'path'];
@@ -465,13 +462,13 @@ export async function isAppInstalled (pkg, opts = {}) {
465
462
  if (util.hasValue(user)) {
466
463
  cmd.push('--user', user);
467
464
  }
468
- /** @type {string} */
469
- let stdout;
465
+ let stdout: string;
470
466
  try {
471
467
  stdout = await this.shell(cmd);
472
468
  } catch (e) {
469
+ const error = e as ExecError;
473
470
  // https://github.com/appium/appium-uiautomator2-driver/issues/810
474
- if (_.includes(e.stderr || e.stdout || e.message, 'access user') && _.isEmpty(user)) {
471
+ if (_.includes(error.stderr || error.stdout || error.message, 'access user') && _.isEmpty(user)) {
475
472
  stdout = await this.shell([...cmd, '--user', '0']);
476
473
  } else {
477
474
  throw e;
@@ -486,12 +483,11 @@ export async function isAppInstalled (pkg, opts = {}) {
486
483
  /**
487
484
  * Start the particular URI on the device under test.
488
485
  *
489
- * @this {import('../adb.js').ADB}
490
- * @param {string} uri - The name of URI to start.
491
- * @param {string?} [pkg=null] - The name of the package to start the URI with.
492
- * @param {import('./types').StartUriOptions} [opts={}]
486
+ * @param uri - The name of URI to start.
487
+ * @param pkg - The name of the package to start the URI with.
488
+ * @param opts - Options for starting the URI
493
489
  */
494
- export async function startUri (uri, pkg = null, opts = {}) {
490
+ export async function startUri (this: ADB, uri: string, pkg: string | null = null, opts: StartUriOptions = {}): Promise<void> {
495
491
  const {
496
492
  waitForLaunch = true,
497
493
  } = opts;
@@ -523,53 +519,51 @@ export async function startUri (uri, pkg = null, opts = {}) {
523
519
  /**
524
520
  * Start the particular package/activity on the device under test.
525
521
  *
526
- * @this {import('../adb.js').ADB}
527
- * @param {import('./types').StartAppOptions} startAppOptions - Startup options mapping.
528
- * @return {Promise<string>} The output of the corresponding adb command.
522
+ * @param startAppOptions - Startup options mapping.
523
+ * @returns The output of the corresponding adb command.
529
524
  * @throws {Error} If there is an error while executing the activity
530
525
  */
531
- export async function startApp (startAppOptions) {
526
+ export async function startApp (this: ADB, startAppOptions: StartAppOptions): Promise<string> {
532
527
  if (!startAppOptions.pkg || !(startAppOptions.activity || startAppOptions.action)) {
533
528
  throw new Error('pkg, and activity or intent action, are required to start an application');
534
529
  }
535
530
 
536
- startAppOptions = _.clone(startAppOptions);
537
- if (startAppOptions.activity) {
538
- startAppOptions.activity = startAppOptions.activity.replace('$', '\\$');
531
+ const options = _.clone(startAppOptions);
532
+ if (options.activity) {
533
+ options.activity = options.activity.replace('$', '\\$');
539
534
  }
540
535
  // initializing defaults
541
- _.defaults(startAppOptions, {
542
- waitPkg: startAppOptions.pkg,
536
+ _.defaults(options, {
537
+ waitPkg: options.pkg,
543
538
  waitForLaunch: true,
544
539
  waitActivity: false,
545
540
  retry: true,
546
541
  stopApp: true
547
542
  });
548
543
  // preventing null waitpkg
549
- startAppOptions.waitPkg = startAppOptions.waitPkg || startAppOptions.pkg;
544
+ options.waitPkg = options.waitPkg || options.pkg;
550
545
 
551
546
  const apiLevel = await this.getApiLevel();
552
- const cmd = buildStartCmd(startAppOptions, apiLevel);
553
- const intentName = `${startAppOptions.action}${startAppOptions.optionalIntentArguments
554
- ? ' ' + startAppOptions.optionalIntentArguments
547
+ const cmd = buildStartCmd(options, apiLevel);
548
+ const intentName = `${options.action}${options.optionalIntentArguments
549
+ ? ' ' + options.optionalIntentArguments
555
550
  : ''}`;
556
551
  try {
557
- const shellOpts = {};
558
- if (_.isInteger(startAppOptions.waitDuration)
559
- // @ts-ignore waitDuration is an integer here
560
- && startAppOptions.waitDuration >= 0) {
561
- shellOpts.timeout = startAppOptions.waitDuration;
552
+ const shellOpts: { timeout?: number } = {};
553
+ if (options.waitDuration !== undefined && _.isInteger(options.waitDuration)
554
+ && options.waitDuration >= 0) {
555
+ shellOpts.timeout = options.waitDuration;
562
556
  }
563
557
  const stdout = await this.shell(cmd, shellOpts);
564
558
  if (stdout.includes('Error: Activity class') && stdout.includes('does not exist')) {
565
- if (startAppOptions.retry && startAppOptions.activity && !startAppOptions.activity.startsWith('.')) {
559
+ if (options.retry && options.activity && !options.activity.startsWith('.')) {
566
560
  log.debug(`We tried to start an activity that doesn't exist, ` +
567
- `retrying with '.${startAppOptions.activity}' activity name`);
568
- startAppOptions.activity = `.${startAppOptions.activity}`;
569
- startAppOptions.retry = false;
570
- return await this.startApp(startAppOptions);
561
+ `retrying with '.${options.activity}' activity name`);
562
+ options.activity = `.${options.activity}`;
563
+ options.retry = false;
564
+ return await this.startApp(options);
571
565
  }
572
- throw new Error(`Activity name '${startAppOptions.activity}' used to start the app doesn't ` +
566
+ throw new Error(`Activity name '${options.activity}' used to start the app doesn't ` +
573
567
  `exist or cannot be launched! Make sure it exists and is a launchable activity`);
574
568
  } else if (stdout.includes('Error: Intent does not match any activities')
575
569
  || stdout.includes('Error: Activity not started, unable to resolve Intent')) {
@@ -577,27 +571,28 @@ export async function startApp (startAppOptions) {
577
571
  `exist or cannot be launched! Make sure it exists and is a launchable activity`);
578
572
  } else if (stdout.includes('java.lang.SecurityException')) {
579
573
  // if the app is disabled on a real device it will throw a security exception
580
- throw new Error(`The permission to start '${startAppOptions.activity}' activity has been denied.` +
574
+ throw new Error(`The permission to start '${options.activity}' activity has been denied.` +
581
575
  `Make sure the activity/package names are correct.`);
582
576
  }
583
- if (startAppOptions.waitActivity) {
584
- await this.waitForActivity(startAppOptions.waitPkg, startAppOptions.waitActivity, startAppOptions.waitDuration);
577
+ if (options.waitActivity) {
578
+ await this.waitForActivity(options.waitPkg, options.waitActivity, options.waitDuration);
585
579
  }
586
580
  return stdout;
587
581
  } catch (e) {
588
- const appDescriptor = startAppOptions.pkg || intentName;
582
+ const error = e as Error;
583
+ const appDescriptor = options.pkg || intentName;
589
584
  throw new Error(`Cannot start the '${appDescriptor}' application. ` +
590
585
  `Consider checking the driver's troubleshooting documentation. ` +
591
- `Original error: ${e.message}`);
586
+ `Original error: ${error.message}`);
592
587
  }
593
588
  }
594
589
 
595
590
  /**
596
591
  * Helper method to call `adb dumpsys window windows/displays`
597
- * @this {import('../adb.js').ADB}
598
- * @returns {Promise<string>}
592
+ *
593
+ * @returns The output of the dumpsys command
599
594
  */
600
- export async function dumpWindows () {
595
+ export async function dumpWindows (this: ADB): Promise<string> {
601
596
  const apiLevel = await this.getApiLevel();
602
597
 
603
598
  // With version 29, Android changed the dumpsys syntax
@@ -610,18 +605,18 @@ export async function dumpWindows () {
610
605
  /**
611
606
  * Get the name of currently focused package and activity.
612
607
  *
613
- * @this {import('../adb.js').ADB}
614
- * @return {Promise<import('./types').PackageActivityInfo>}
608
+ * @returns The focused package and activity information
615
609
  * @throws {Error} If there is an error while parsing the data.
616
610
  */
617
- export async function getFocusedPackageAndActivity () {
611
+ export async function getFocusedPackageAndActivity (this: ADB): Promise<PackageActivityInfo> {
618
612
  log.debug('Getting focused package and activity');
619
- let stdout;
613
+ let stdout: string;
620
614
  try {
621
615
  stdout = await this.dumpWindows();
622
616
  } catch (e) {
617
+ const error = e as Error;
623
618
  throw new Error(
624
- `Could not retrieve the currently focused package and activity. Original error: ${e.message}`
619
+ `Could not retrieve the currently focused package and activity. Original error: ${error.message}`
625
620
  );
626
621
  }
627
622
 
@@ -634,17 +629,14 @@ export async function getFocusedPackageAndActivity () {
634
629
  const nullCurrentFocusRe = /^\s*mCurrentFocus=null/m;
635
630
  const currentFocusAppRe = new RegExp('^\\s*mCurrentFocus.+\\{.+\\s([^\\s\\/]+)\\/([^\\s]+)\\b', 'mg');
636
631
 
637
- /** @type {import('./types').PackageActivityInfo[]} */
638
- const focusedAppCandidates = [];
639
- /** @type {import('./types').PackageActivityInfo[]} */
640
- const currentFocusAppCandidates = [];
641
- /** @type {[import('./types').PackageActivityInfo[], RegExp][]} */
642
- const pairs = [
632
+ const focusedAppCandidates: PackageActivityInfo[] = [];
633
+ const currentFocusAppCandidates: PackageActivityInfo[] = [];
634
+ const pairs: [PackageActivityInfo[], RegExp][] = [
643
635
  [focusedAppCandidates, focusedAppRe],
644
636
  [currentFocusAppCandidates, currentFocusAppRe]
645
637
  ];
646
638
  for (const [candidates, pattern] of pairs) {
647
- let match;
639
+ let match: RegExpExecArray | null;
648
640
  while ((match = pattern.exec(stdout))) {
649
641
  candidates.push({
650
642
  appPackage: match[1].trim(),
@@ -685,28 +677,26 @@ export async function getFocusedPackageAndActivity () {
685
677
  /**
686
678
  * Wait for the given activity to be focused/non-focused.
687
679
  *
688
- * @this {import('../adb.js').ADB}
689
- * @param {string} pkg - The name of the package to wait for.
690
- * @param {string} activity - The name of the activity, belonging to that package,
680
+ * @param pkg - The name of the package to wait for.
681
+ * @param activity - The name of the activity, belonging to that package,
691
682
  * to wait for.
692
- * @param {boolean} waitForStop - Whether to wait until the activity is focused (true)
683
+ * @param waitForStop - Whether to wait until the activity is focused (true)
693
684
  * or is not focused (false).
694
- * @param {number} [waitMs=20000] - Number of milliseconds to wait before timeout occurs.
695
- * @throws {error} If timeout happens.
685
+ * @param waitMs - Number of milliseconds to wait before timeout occurs.
686
+ * @throws {Error} If timeout happens.
696
687
  */
697
- export async function waitForActivityOrNot (pkg, activity, waitForStop, waitMs = 20000) {
688
+ export async function waitForActivityOrNot (this: ADB, pkg: string, activity: string, waitForStop: boolean, waitMs: number = 20000): Promise<void> {
698
689
  if (!pkg || !activity) {
699
690
  throw new Error('Package and activity required.');
700
691
  }
701
692
 
702
- const splitNames = (/** @type {string} */ names) => names.split(',').map(_.trim);
693
+ const splitNames = (names: string) => names.split(',').map(_.trim);
703
694
  const allPackages = splitNames(pkg);
704
695
  const allActivities = splitNames(activity);
705
696
 
706
- const toFullyQualifiedActivityName = (/** @type {string} */ prefix, /** @type {string} */ suffix) =>
697
+ const toFullyQualifiedActivityName = (prefix: string, suffix: string) =>
707
698
  `${prefix}${suffix}`.replace(/\/\.?/g, '.').replace(/\.{2,}/g, '.');
708
- /** @type {Set<string>} */
709
- const possibleActivityNamesSet = new Set();
699
+ const possibleActivityNamesSet = new Set<string>();
710
700
  for (const oneActivity of allActivities) {
711
701
  if (oneActivity.startsWith('.')) {
712
702
  // add the package name if activity is not full qualified
@@ -741,12 +731,13 @@ export async function waitForActivityOrNot (pkg, activity, waitForStop, waitMs =
741
731
  );
742
732
 
743
733
  const conditionFunc = async () => {
744
- let appPackage;
745
- let appActivity;
734
+ let appPackage: string | null | undefined;
735
+ let appActivity: string | null | undefined;
746
736
  try {
747
737
  ({appPackage, appActivity} = await this.getFocusedPackageAndActivity());
748
738
  } catch (e) {
749
- log.debug(e.message);
739
+ const error = e as Error;
740
+ log.debug(error.message);
750
741
  return false;
751
742
  }
752
743
  if (appActivity && appPackage) {
@@ -785,42 +776,38 @@ export async function waitForActivityOrNot (pkg, activity, waitForStop, waitMs =
785
776
  /**
786
777
  * Wait for the given activity to be focused
787
778
  *
788
- * @this {import('../adb.js').ADB}
789
- * @param {string} pkg - The name of the package to wait for.
790
- * @param {string} act - The name of the activity, belonging to that package,
779
+ * @param pkg - The name of the package to wait for.
780
+ * @param act - The name of the activity, belonging to that package,
791
781
  * to wait for.
792
- * @param {number} [waitMs=20000] - Number of milliseconds to wait before timeout occurs.
793
- * @throws {error} If timeout happens.
782
+ * @param waitMs - Number of milliseconds to wait before timeout occurs.
783
+ * @throws {Error} If timeout happens.
794
784
  */
795
- export async function waitForActivity (pkg, act, waitMs = 20000) {
785
+ export async function waitForActivity (this: ADB, pkg: string, act: string, waitMs: number = 20000): Promise<void> {
796
786
  await this.waitForActivityOrNot(pkg, act, false, waitMs);
797
787
  }
798
788
 
799
789
  /**
800
790
  * Wait for the given activity to be non-focused.
801
791
  *
802
- * @this {import('../adb.js').ADB}
803
- * @param {string} pkg - The name of the package to wait for.
804
- * @param {string} act - The name of the activity, belonging to that package,
792
+ * @param pkg - The name of the package to wait for.
793
+ * @param act - The name of the activity, belonging to that package,
805
794
  * to wait for.
806
- * @param {number} [waitMs=20000] - Number of milliseconds to wait before timeout occurs.
807
- * @throws {error} If timeout happens.
795
+ * @param waitMs - Number of milliseconds to wait before timeout occurs.
796
+ * @throws {Error} If timeout happens.
808
797
  */
809
- export async function waitForNotActivity (pkg, act, waitMs = 20000) {
798
+ export async function waitForNotActivity (this: ADB, pkg: string, act: string, waitMs: number = 20000): Promise<void> {
810
799
  await this.waitForActivityOrNot(pkg, act, true, waitMs);
811
800
  }
812
801
 
813
- // #region Private functions
814
-
815
802
  /**
816
803
  * Builds command line representation for the given
817
804
  * application startup options
818
805
  *
819
- * @param {StartCmdOptions} startAppOptions - Application options mapping
820
- * @param {number} apiLevel - The actual OS API level
821
- * @returns {string[]} The actual command line array
806
+ * @param startAppOptions - Application options mapping
807
+ * @param apiLevel - The actual OS API level
808
+ * @returns The actual command line array
822
809
  */
823
- export function buildStartCmd (startAppOptions, apiLevel) {
810
+ export function buildStartCmd (startAppOptions: StartCmdOptions, apiLevel: number): string[] {
824
811
  const {
825
812
  user,
826
813
  waitForLaunch,
@@ -860,70 +847,12 @@ export function buildStartCmd (startAppOptions, apiLevel) {
860
847
  return cmd;
861
848
  }
862
849
 
863
- /**
864
- *
865
- * @param {string} value expect optionalIntentArguments to be a single string of the form:
866
- * "-flag key"
867
- * "-flag key value"
868
- * or a combination of these (e.g., "-flag1 key1 -flag2 key2 value2")
869
- * @returns {string[]}
870
- */
871
- function parseOptionalIntentArguments(value) {
872
- // take a string and parse out the part before any spaces, and anything after
873
- // the first space
874
- /** @type {(str: string) => string[]} */
875
- const parseKeyValue = (str) => {
876
- str = str.trim();
877
- const spacePos = str.indexOf(' ');
878
- if (spacePos < 0) {
879
- return str.length ? [str] : [];
880
- } else {
881
- return [str.substring(0, spacePos).trim(), str.substring(spacePos + 1).trim()];
882
- }
883
- };
884
-
885
- // cycle through the optionalIntentArguments and pull out the arguments
886
- // add a space initially so flags can be distinguished from arguments that
887
- // have internal hyphens
888
- let optionalIntentArguments = ` ${value}`;
889
- const re = / (-[^\s]+) (.+)/;
890
- /** @type {string[]} */
891
- const result = [];
892
- while (true) {
893
- const args = re.exec(optionalIntentArguments);
894
- if (!args) {
895
- if (optionalIntentArguments.length) {
896
- // no more flags, so the remainder can be treated as 'key' or 'key value'
897
- result.push(...parseKeyValue(optionalIntentArguments));
898
- }
899
- // we are done
900
- return result;
901
- }
902
-
903
- // take the flag and see if it is at the beginning of the string
904
- // if it is not, then it means we have been through already, and
905
- // what is before the flag is the argument for the previous flag
906
- const flag = args[1];
907
- const flagPos = optionalIntentArguments.indexOf(flag);
908
- if (flagPos !== 0) {
909
- const prevArgs = optionalIntentArguments.substring(0, flagPos);
910
- result.push(...parseKeyValue(prevArgs));
911
- }
912
-
913
- // add the flag, as there are no more earlier arguments
914
- result.push(flag);
915
-
916
- // make optionalIntentArguments hold the remainder
917
- optionalIntentArguments = args[2];
918
- }
919
- }
920
-
921
850
  /**
922
851
  * Parses the name of launchable package activity
923
852
  * from dumpsys output.
924
853
  *
925
- * @param {string} dumpsys the actual dumpsys output
926
- * @returns {string[]} Either the fully qualified
854
+ * @param dumpsys - The actual dumpsys output
855
+ * @returns Either the fully qualified
927
856
  * activity name as a single list item or an empty list if nothing could be parsed.
928
857
  * In Android 6 and older there is no reliable way to determine
929
858
  * the category name for the given activity, so this API just
@@ -931,12 +860,12 @@ function parseOptionalIntentArguments(value) {
931
860
  * with the expectation that the app manifest could be parsed next
932
861
  * in order to determine category names for these.
933
862
  */
934
- export function parseLaunchableActivityNames (dumpsys) {
863
+ export function parseLaunchableActivityNames (dumpsys: string): string[] {
935
864
  const mainActivityNameRe = new RegExp(`^\\s*${_.escapeRegExp(MAIN_ACTION)}:$`);
936
865
  const categoryNameRe = /^\s*Category:\s+"([a-zA-Z0-9._/-]+)"$/;
937
- const blocks = [];
938
- let blockStartIndent;
939
- let block = [];
866
+ const blocks: string[][] = [];
867
+ let blockStartIndent: number | null | undefined;
868
+ let block: string[] = [];
940
869
  for (const line of dumpsys.split('\n').map(_.trimEnd)) {
941
870
  const currentIndent = line.length - _.trimStart(line).length;
942
871
  if (mainActivityNameRe.test(line)) {
@@ -965,7 +894,7 @@ export function parseLaunchableActivityNames (dumpsys) {
965
894
  blocks.push(block);
966
895
  }
967
896
 
968
- const result = [];
897
+ const result: string[] = [];
969
898
  for (const item of blocks) {
970
899
  let hasCategory = false;
971
900
  let isLauncherCategory = false;
@@ -1004,48 +933,30 @@ export function parseLaunchableActivityNames (dumpsys) {
1004
933
  /**
1005
934
  * Check if the given string is a valid component name
1006
935
  *
1007
- * @param {string} classString The string to verify
1008
- * @return {RegExpExecArray?} The result of Regexp.exec operation
936
+ * @param classString - The string to verify
937
+ * @returns The result of Regexp.exec operation
1009
938
  * or _null_ if no matches are found
1010
939
  */
1011
- export function matchComponentName (classString) {
940
+ export function matchComponentName (classString: string): RegExpExecArray | null {
1012
941
  // some.package/some.package.Activity
1013
942
  return /^[\p{L}0-9./_]+$/u.exec(classString);
1014
943
  }
1015
944
 
1016
- /**
1017
- * Escapes special characters in command line arguments.
1018
- * This is needed to avoid possible issues with how system `spawn`
1019
- * call handles them.
1020
- * See https://discuss.appium.io/t/how-to-modify-wd-proxy-and-uiautomator2-source-code-to-support-unicode/33466
1021
- * for more details.
1022
- *
1023
- * @param {string} arg Non-escaped argument string
1024
- * @returns The escaped argument
1025
- */
1026
- function escapeShellArg (arg) {
1027
- arg = `${arg}`;
1028
- if (system.isWindows()) {
1029
- return /[&|^\s]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg;
1030
- }
1031
- return arg.replace(/&/g, '\\&');
1032
- }
1033
-
1034
945
  /**
1035
946
  * Retrieves the list of permission names encoded in `dumpsys package` command output.
1036
947
  *
1037
- * @param {string} dumpsysOutput - The actual command output.
1038
- * @param {string[]} groupNames - The list of group names to list permissions for.
1039
- * @param {boolean?} [grantedState=null] - The expected state of `granted` attribute to filter with.
948
+ * @param dumpsysOutput - The actual command output.
949
+ * @param groupNames - The list of group names to list permissions for.
950
+ * @param grantedState - The expected state of `granted` attribute to filter with.
1040
951
  * No filtering is done if the parameter is not set.
1041
- * @returns {string[]} The list of matched permission names or an empty list if no matches were found.
952
+ * @returns The list of matched permission names or an empty list if no matches were found.
1042
953
  */
1043
- export function extractMatchingPermissions (dumpsysOutput, groupNames, grantedState = null) {
1044
- const groupPatternByName = (groupName) => new RegExp(`^(\\s*${_.escapeRegExp(groupName)} permissions:[\\s\\S]+)`, 'm');
954
+ export function extractMatchingPermissions (dumpsysOutput: string, groupNames: string[], grantedState: boolean | null = null): string[] {
955
+ const groupPatternByName = (groupName: string) => new RegExp(`^(\\s*${_.escapeRegExp(groupName)} permissions:[\\s\\S]+)`, 'm');
1045
956
  const indentPattern = /\S|$/;
1046
957
  const permissionNamePattern = /android\.\w*\.?permission\.\w+/;
1047
958
  const grantedStatePattern = /\bgranted=(\w+)/;
1048
- const result = [];
959
+ const result: Array<{ permission: string; granted?: boolean }> = [];
1049
960
  for (const groupName of groupNames) {
1050
961
  const groupMatch = groupPatternByName(groupName).exec(dumpsysOutput);
1051
962
  if (!groupMatch) {
@@ -1068,7 +979,7 @@ export function extractMatchingPermissions (dumpsysOutput, groupNames, grantedSt
1068
979
  if (!permissionNameMatch) {
1069
980
  continue;
1070
981
  }
1071
- const item = {
982
+ const item: { permission: string; granted?: boolean } = {
1072
983
  permission: permissionNameMatch[0],
1073
984
  };
1074
985
  const grantedStateMatch = grantedStatePattern.exec(line);
@@ -1090,11 +1001,10 @@ export function extractMatchingPermissions (dumpsysOutput, groupNames, grantedSt
1090
1001
  /**
1091
1002
  * Broadcast a message to the given intent.
1092
1003
  *
1093
- * @this {import('../adb.js').ADB}
1094
- * @param {string} intent - The name of the intent to broadcast to.
1095
- * @throws {error} If intent name is not a valid class name.
1004
+ * @param intent - The name of the intent to broadcast to.
1005
+ * @throws {Error} If intent name is not a valid class name.
1096
1006
  */
1097
- export async function broadcast (intent) {
1007
+ export async function broadcast (this: ADB, intent: string): Promise<void> {
1098
1008
  if (!this.isValidClass(intent)) {
1099
1009
  throw new Error(`Invalid intent ${intent}`);
1100
1010
  }
@@ -1105,11 +1015,10 @@ export async function broadcast (intent) {
1105
1015
  /**
1106
1016
  * Get the list of process ids for the particular package on the device under test.
1107
1017
  *
1108
- * @this {import('../adb.js').ADB}
1109
- * @param {string} pkg
1110
- * @returns {Promise<number[]>} The list of matched process IDs or an empty list.
1018
+ * @param pkg - The package name
1019
+ * @returns The list of matched process IDs or an empty list.
1111
1020
  */
1112
- export async function listAppProcessIds (pkg) {
1021
+ export async function listAppProcessIds (this: ADB, pkg: string): Promise<number[]> {
1113
1022
  log.debug(`Getting IDs of all '${pkg}' package`);
1114
1023
  const pidRegex = new RegExp(`ProcessRecord\\{[\\w]+\\s+(\\d+):${_.escapeRegExp(pkg)}\\/`);
1115
1024
  const processesInfo = await this.shell(['dumpsys', 'activity', 'processes']);
@@ -1124,25 +1033,101 @@ export async function listAppProcessIds (pkg) {
1124
1033
  * Check whether the process with the particular name is running on the device
1125
1034
  * under test.
1126
1035
  *
1127
- * @this {import('../adb.js').ADB}
1128
- * @param {string} pkg - The id of the package to be checked.
1036
+ * @param pkg - The id of the package to be checked.
1129
1037
  * @returns True if the given package is running.
1130
1038
  */
1131
- export async function isAppRunning (pkg) {
1039
+ export async function isAppRunning (this: ADB, pkg: string): Promise<boolean> {
1132
1040
  return !_.isEmpty(await this.listAppProcessIds(pkg));
1133
1041
  }
1134
1042
 
1043
+ // Private methods
1044
+
1045
+ /**
1046
+ * Parses optional intent arguments from a string.
1047
+ *
1048
+ * @param value - Expect optionalIntentArguments to be a single string of the form:
1049
+ * "-flag key"
1050
+ * "-flag key value"
1051
+ * or a combination of these (e.g., "-flag1 key1 -flag2 key2 value2")
1052
+ * @returns Parsed arguments array
1053
+ */
1054
+ function parseOptionalIntentArguments(value: string): string[] {
1055
+ // take a string and parse out the part before any spaces, and anything after
1056
+ // the first space
1057
+ const parseKeyValue = (str: string): string[] => {
1058
+ str = str.trim();
1059
+ const spacePos = str.indexOf(' ');
1060
+ if (spacePos < 0) {
1061
+ return str.length ? [str] : [];
1062
+ } else {
1063
+ return [str.substring(0, spacePos).trim(), str.substring(spacePos + 1).trim()];
1064
+ }
1065
+ };
1066
+
1067
+ // cycle through the optionalIntentArguments and pull out the arguments
1068
+ // add a space initially so flags can be distinguished from arguments that
1069
+ // have internal hyphens
1070
+ let optionalIntentArguments = ` ${value}`;
1071
+ const re = / (-[^\s]+) (.+)/;
1072
+ const result: string[] = [];
1073
+ while (true) {
1074
+ const args = re.exec(optionalIntentArguments);
1075
+ if (!args) {
1076
+ if (optionalIntentArguments.length) {
1077
+ // no more flags, so the remainder can be treated as 'key' or 'key value'
1078
+ result.push(...parseKeyValue(optionalIntentArguments));
1079
+ }
1080
+ // we are done
1081
+ return result;
1082
+ }
1083
+
1084
+ // take the flag and see if it is at the beginning of the string
1085
+ // if it is not, then it means we have been through already, and
1086
+ // what is before the flag is the argument for the previous flag
1087
+ const flag = args[1];
1088
+ const flagPos = optionalIntentArguments.indexOf(flag);
1089
+ if (flagPos !== 0) {
1090
+ const prevArgs = optionalIntentArguments.substring(0, flagPos);
1091
+ result.push(...parseKeyValue(prevArgs));
1092
+ }
1093
+
1094
+ // add the flag, as there are no more earlier arguments
1095
+ result.push(flag);
1096
+
1097
+ // make optionalIntentArguments hold the remainder
1098
+ optionalIntentArguments = args[2];
1099
+ }
1100
+ }
1101
+
1135
1102
  /**
1136
- * @typedef {Object} StartCmdOptions
1137
- * @property {number|string} [user]
1138
- * @property {boolean} [waitForLaunch]
1139
- * @property {string} [pkg]
1140
- * @property {string} [activity]
1141
- * @property {string} [action]
1142
- * @property {string} [category]
1143
- * @property {boolean} [stopApp]
1144
- * @property {string} [flags]
1145
- * @property {string} [optionalIntentArguments]
1103
+ * Escapes special characters in command line arguments.
1104
+ * This is needed to avoid possible issues with how system `spawn`
1105
+ * call handles them.
1106
+ * See https://discuss.appium.io/t/how-to-modify-wd-proxy-and-uiautomator2-source-code-to-support-unicode/33466
1107
+ * for more details.
1108
+ *
1109
+ * @param arg - Non-escaped argument string
1110
+ * @returns The escaped argument
1146
1111
  */
1112
+ function escapeShellArg (arg: string): string {
1113
+ arg = `${arg}`;
1114
+ if (system.isWindows()) {
1115
+ return /[&|^\s]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg;
1116
+ }
1117
+ return arg.replace(/&/g, '\\&');
1118
+ }
1119
+
1120
+ // Type definitions
1121
+
1122
+ export interface StartCmdOptions {
1123
+ user?: number | string;
1124
+ waitForLaunch?: boolean;
1125
+ pkg?: string;
1126
+ activity?: string;
1127
+ action?: string;
1128
+ category?: string;
1129
+ stopApp?: boolean;
1130
+ flags?: string;
1131
+ optionalIntentArguments?: string;
1132
+ }
1147
1133
 
1148
- // #endregion