appium-android-driver 12.4.7 → 12.4.8

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.
@@ -0,0 +1,422 @@
1
+ import _ from 'lodash';
2
+ import {errors} from 'appium/driver';
3
+ import {util} from '@appium/support';
4
+ import type {AndroidDriver} from '../driver';
5
+ import type {IntentOpts} from './types';
6
+
7
+ const NO_VALUE_ARG_TYPE = 'sn';
8
+ const SUPPORTED_EXTRA_TYPES = [
9
+ 's',
10
+ NO_VALUE_ARG_TYPE,
11
+ 'z',
12
+ 'i',
13
+ 'l',
14
+ 'f',
15
+ 'u',
16
+ 'cn',
17
+ 'ia',
18
+ 'ial',
19
+ 'la',
20
+ 'lal',
21
+ 'fa',
22
+ 'fal',
23
+ 'sa',
24
+ 'sal',
25
+ ] as const;
26
+
27
+ /**
28
+ * Starts an Android activity.
29
+ *
30
+ * @deprecated Use {@link mobileStartActivity} instead.
31
+ * @param appPackage The package name of the application to start.
32
+ * @param appActivity The activity name to start.
33
+ * @param appWaitPackage The package name to wait for. Defaults to `appPackage` if not provided.
34
+ * @param appWaitActivity The activity name to wait for. Defaults to `appActivity` if not provided.
35
+ * @param intentAction The intent action to use.
36
+ * @param intentCategory The intent category to use.
37
+ * @param intentFlags The intent flags to use.
38
+ * @param optionalIntentArguments Optional intent arguments.
39
+ * @param dontStopAppOnReset If `true`, does not stop the app on reset. If not provided, uses the capability value.
40
+ * @returns Promise that resolves when the activity is started.
41
+ */
42
+ export async function startActivity(
43
+ this: AndroidDriver,
44
+ appPackage: string,
45
+ appActivity: string,
46
+ appWaitPackage?: string,
47
+ appWaitActivity?: string,
48
+ intentAction?: string,
49
+ intentCategory?: string,
50
+ intentFlags?: string,
51
+ optionalIntentArguments?: string,
52
+ dontStopAppOnReset?: boolean,
53
+ ): Promise<void> {
54
+ this.log.debug(`Starting package '${appPackage}' and activity '${appActivity}'`);
55
+
56
+ // dontStopAppOnReset is both an argument here, and a desired capability
57
+ // if the argument is set, use it, otherwise use the cap
58
+ if (!util.hasValue(dontStopAppOnReset)) {
59
+ dontStopAppOnReset = !!this.opts.dontStopAppOnReset;
60
+ }
61
+
62
+ const args = {
63
+ pkg: appPackage,
64
+ activity: appActivity,
65
+ waitPkg: appWaitPackage || appPackage,
66
+ waitActivity: appWaitActivity || appActivity,
67
+ action: intentAction,
68
+ category: intentCategory,
69
+ flags: intentFlags,
70
+ optionalIntentArguments,
71
+ stopApp: !dontStopAppOnReset,
72
+ };
73
+ this._cachedActivityArgs = this._cachedActivityArgs || {};
74
+ this._cachedActivityArgs[`${args.waitPkg}/${args.waitActivity}`] = args;
75
+ await this.adb.startApp(args);
76
+ }
77
+
78
+ /**
79
+ * Starts an Android activity using the activity manager.
80
+ *
81
+ * @param wait Set it to `true` if you want to block the method call
82
+ * until the activity manager's process returns the control to the system.
83
+ * `false` by default.
84
+ * @param stop Set it to `true` to force stop the target
85
+ * app before starting the activity. `false` by default.
86
+ * @param windowingMode The windowing mode to launch the activity into.
87
+ * Check https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/WindowConfiguration.java
88
+ * for more details on possible windowing modes (constants starting with `WINDOWING_MODE_`).
89
+ * @param activityType The activity type to launch the activity as.
90
+ * Check https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/WindowConfiguration.java
91
+ * for more details on possible activity types (constants starting with `ACTIVITY_TYPE_`).
92
+ * @param display The display identifier to launch the activity into.
93
+ * @param user The user ID for which the activity is started.
94
+ * @param intent The name of the activity intent to start, for example
95
+ * `com.some.package.name/.YourActivitySubClassName`.
96
+ * @param action Action name.
97
+ * @param pkg Package name.
98
+ * @param uri Unified resource identifier.
99
+ * @param mimeType Mime type.
100
+ * @param identifier Optional identifier.
101
+ * @param component Component name.
102
+ * @param categories One or more category names.
103
+ * @param extras Optional intent arguments. Must be represented as array of arrays,
104
+ * where each subarray item contains two or three string items: value type, key name and the value itself.
105
+ * See {@link IntentOpts} for supported value types.
106
+ * @param flags Intent startup-specific flags as a hexadecimal string.
107
+ * @returns Promise that resolves to the command output string.
108
+ */
109
+ export async function mobileStartActivity(
110
+ this: AndroidDriver,
111
+ wait?: boolean,
112
+ stop?: boolean,
113
+ windowingMode?: string | number,
114
+ activityType?: string | number,
115
+ display?: number | string,
116
+ user?: string,
117
+ intent?: string,
118
+ action?: string,
119
+ pkg?: string,
120
+ uri?: string,
121
+ mimeType?: string,
122
+ identifier?: string,
123
+ component?: string,
124
+ categories?: string | string[],
125
+ extras?: string[][],
126
+ flags?: string,
127
+ ): Promise<string> {
128
+ const cmd = [
129
+ 'am',
130
+ 'start-activity',
131
+ ];
132
+ if (!_.isNil(user)) {
133
+ cmd.push('--user', String(user));
134
+ }
135
+ if (wait) {
136
+ cmd.push('-W');
137
+ }
138
+ if (stop) {
139
+ cmd.push('-S');
140
+ }
141
+ if (!_.isNil(windowingMode)) {
142
+ cmd.push('--windowingMode', String(windowingMode));
143
+ }
144
+ if (!_.isNil(activityType)) {
145
+ cmd.push('--activityType', String(activityType));
146
+ }
147
+ if (!_.isNil(display)) {
148
+ cmd.push('--display', String(display));
149
+ }
150
+ cmd.push(...parseIntentSpec({
151
+ intent,
152
+ action,
153
+ package: pkg,
154
+ uri,
155
+ mimeType,
156
+ identifier,
157
+ component,
158
+ categories,
159
+ extras,
160
+ flags,
161
+ }));
162
+ return await this.adb.shell(cmd);
163
+ }
164
+
165
+ /**
166
+ * Sends a broadcast intent to the Android system.
167
+ *
168
+ * @param receiverPermission Require receiver to hold the given permission.
169
+ * @param allowBackgroundActivityStarts Whether the receiver may start activities even if in the background.
170
+ * @param user The user ID for which the broadcast is sent.
171
+ * The `current` alias assumes the current user ID. `all` by default.
172
+ * @param intent The name of the activity intent to broadcast, for example
173
+ * `com.some.package.name/.YourServiceSubClassName`.
174
+ * @param action Action name.
175
+ * @param pkg Package name.
176
+ * @param uri Unified resource identifier.
177
+ * @param mimeType Mime type.
178
+ * @param identifier Optional identifier.
179
+ * @param component Component name.
180
+ * @param categories One or more category names.
181
+ * @param extras Optional intent arguments. Must be represented as array of arrays,
182
+ * where each subarray item contains two or three string items: value type, key name and the value itself.
183
+ * See {@link IntentOpts} for supported value types.
184
+ * @param flags Intent startup-specific flags as a hexadecimal string.
185
+ * @returns Promise that resolves to the command output string.
186
+ */
187
+ export async function mobileBroadcast(
188
+ this: AndroidDriver,
189
+ receiverPermission?: string,
190
+ allowBackgroundActivityStarts?: boolean,
191
+ user?: string | number,
192
+ intent?: string,
193
+ action?: string,
194
+ pkg?: string,
195
+ uri?: string,
196
+ mimeType?: string,
197
+ identifier?: string,
198
+ component?: string,
199
+ categories?: string | string[],
200
+ extras?: string[][],
201
+ flags?: string,
202
+ ): Promise<string> {
203
+ const cmd = ['am', 'broadcast'];
204
+ if (!_.isNil(user)) {
205
+ cmd.push('--user', String(user));
206
+ }
207
+ if (receiverPermission) {
208
+ cmd.push('--receiver-permission', receiverPermission);
209
+ }
210
+ if (allowBackgroundActivityStarts) {
211
+ cmd.push('--allow-background-activity-starts');
212
+ }
213
+ cmd.push(...parseIntentSpec({
214
+ intent,
215
+ action,
216
+ package: pkg,
217
+ uri,
218
+ mimeType,
219
+ identifier,
220
+ component,
221
+ categories,
222
+ extras,
223
+ flags,
224
+ }));
225
+ return await this.adb.shell(cmd);
226
+ }
227
+
228
+ /**
229
+ * Starts an Android service.
230
+ *
231
+ * @param foreground Set it to `true` if your service must be started as foreground service.
232
+ * This option is ignored if the API level of the device under test is below 26 (Android 8).
233
+ * @param user The user ID for which the service is started.
234
+ * The `current` user id is used by default.
235
+ * @param intent The name of the activity intent to start, for example
236
+ * `com.some.package.name/.YourServiceSubClassName`.
237
+ * @param action Action name.
238
+ * @param pkg Package name.
239
+ * @param uri Unified resource identifier.
240
+ * @param mimeType Mime type.
241
+ * @param identifier Optional identifier.
242
+ * @param component Component name.
243
+ * @param categories One or more category names.
244
+ * @param extras Optional intent arguments. Must be represented as array of arrays,
245
+ * where each subarray item contains two or three string items: value type, key name and the value itself.
246
+ * See {@link IntentOpts} for supported value types.
247
+ * @param flags Intent startup-specific flags as a hexadecimal string.
248
+ * @returns Promise that resolves to the command output string.
249
+ */
250
+ export async function mobileStartService(
251
+ this: AndroidDriver,
252
+ foreground?: boolean,
253
+ user?: string,
254
+ intent?: string,
255
+ action?: string,
256
+ pkg?: string,
257
+ uri?: string,
258
+ mimeType?: string,
259
+ identifier?: string,
260
+ component?: string,
261
+ categories?: string | string[],
262
+ extras?: string[][],
263
+ flags?: string,
264
+ ): Promise<string> {
265
+ const cmd = ['am'];
266
+ cmd.push(foreground ? 'start-foreground-service' : 'start-service');
267
+ if (!_.isNil(user)) {
268
+ cmd.push('--user', String(user));
269
+ }
270
+ cmd.push(...parseIntentSpec({
271
+ intent,
272
+ action,
273
+ package: pkg,
274
+ uri,
275
+ mimeType,
276
+ identifier,
277
+ component,
278
+ categories,
279
+ extras,
280
+ flags,
281
+ }));
282
+ return await this.adb.shell(cmd);
283
+ }
284
+
285
+ /**
286
+ * Stops an Android service.
287
+ *
288
+ * @param user The user ID for which the service is stopped.
289
+ * @param intent The name of the activity intent to stop, for example
290
+ * `com.some.package.name/.YourServiceSubClassName`.
291
+ * @param action Action name.
292
+ * @param pkg Package name.
293
+ * @param uri Unified resource identifier.
294
+ * @param mimeType Mime type.
295
+ * @param identifier Optional identifier.
296
+ * @param component Component name.
297
+ * @param categories One or more category names.
298
+ * @param extras Optional intent arguments. Must be represented as array of arrays,
299
+ * where each subarray item contains two or three string items: value type, key name and the value itself.
300
+ * See {@link IntentOpts} for supported value types.
301
+ * @param flags Intent startup-specific flags as a hexadecimal string.
302
+ * @returns Promise that resolves to the command output string.
303
+ * If the service was already stopped, returns the error message.
304
+ */
305
+ export async function mobileStopService(
306
+ this: AndroidDriver,
307
+ user?: string,
308
+ intent?: string,
309
+ action?: string,
310
+ pkg?: string,
311
+ uri?: string,
312
+ mimeType?: string,
313
+ identifier?: string,
314
+ component?: string,
315
+ categories?: string | string[],
316
+ extras?: string[][],
317
+ flags?: string,
318
+ ): Promise<string> {
319
+ const cmd = [
320
+ 'am',
321
+ 'stop-service',
322
+ ];
323
+ if (!_.isNil(user)) {
324
+ cmd.push('--user', String(user));
325
+ }
326
+ cmd.push(...parseIntentSpec({
327
+ intent,
328
+ action,
329
+ package: pkg,
330
+ uri,
331
+ mimeType,
332
+ identifier,
333
+ component,
334
+ categories,
335
+ extras,
336
+ flags,
337
+ }));
338
+ try {
339
+ return await this.adb.shell(cmd);
340
+ } catch (e) {
341
+ // https://github.com/appium/appium-uiautomator2-driver/issues/792
342
+ const err = e as {code?: number; stderr?: string};
343
+ if (err.code === 255 && err.stderr?.includes('Service stopped')) {
344
+ return err.stderr;
345
+ }
346
+ throw e;
347
+ }
348
+ }
349
+
350
+ // #region Internal helpers
351
+
352
+ function parseIntentSpec(opts: IntentOpts = {}): string[] {
353
+ const {intent, action, uri, mimeType, identifier, categories, component, extras, flags} = opts;
354
+ const resultArgs: string[] = [];
355
+ if (intent) {
356
+ resultArgs.push(intent);
357
+ }
358
+ if (action) {
359
+ resultArgs.push('-a', action);
360
+ }
361
+ if (uri) {
362
+ resultArgs.push('-d', uri);
363
+ }
364
+ if (mimeType) {
365
+ resultArgs.push('-t', mimeType);
366
+ }
367
+ if (!_.isNil(identifier)) {
368
+ resultArgs.push('-i', identifier);
369
+ }
370
+ if (categories) {
371
+ if (_.isArray(categories)) {
372
+ resultArgs.push(..._.flatMap(categories.map((cName) => ['-c', cName])));
373
+ } else {
374
+ resultArgs.push('-c', categories);
375
+ }
376
+ }
377
+ if (component) {
378
+ resultArgs.push('-n', component);
379
+ }
380
+ if (opts.package) {
381
+ resultArgs.push('-p', opts.package);
382
+ }
383
+ if (extras) {
384
+ if (!_.isArray(extras)) {
385
+ throw new errors.InvalidArgumentError(`'extras' must be an array`);
386
+ }
387
+ for (const item of extras) {
388
+ if (!_.isArray(item)) {
389
+ throw new errors.InvalidArgumentError(`Extra argument '${item}' must be an array`);
390
+ }
391
+ const [type, key, value] = item;
392
+ if (!SUPPORTED_EXTRA_TYPES.includes(type as any)) {
393
+ throw new errors.InvalidArgumentError(
394
+ `Extra argument type '${type}' is not known. ` +
395
+ `Supported intent argument types are: ${SUPPORTED_EXTRA_TYPES.join(', ')}`,
396
+ );
397
+ }
398
+ if (_.isEmpty(key) || (_.isString(key) && _.trim(key) === '')) {
399
+ throw new errors.InvalidArgumentError(
400
+ `Extra argument's key in '${JSON.stringify(item)}' must be a valid string identifier`,
401
+ );
402
+ }
403
+ if (type === NO_VALUE_ARG_TYPE) {
404
+ resultArgs.push(`--e${type}`, key);
405
+ } else if (_.isUndefined(value)) {
406
+ throw new errors.InvalidArgumentError(
407
+ `Intent argument type '${type}' in '${JSON.stringify(item)}' requires a ` +
408
+ `valid value to be provided`,
409
+ );
410
+ } else {
411
+ resultArgs.push(`--e${type}`, key, value);
412
+ }
413
+ }
414
+ }
415
+ if (flags) {
416
+ resultArgs.push('-f', flags);
417
+ }
418
+ return resultArgs;
419
+ }
420
+
421
+ // #endregion
422
+