@yume-chan/android-bin 0.0.15 → 0.0.17

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/src/demo-mode.ts CHANGED
@@ -1,179 +1,179 @@
1
- // cspell: ignore carriernetworkchange
2
- // cspell: ignore powersave
3
- // cspell: ignore nosim
4
- // cspell: ignore systemui
5
- // cspell: ignore sysui
6
-
7
- import { Adb, AdbCommandBase } from '@yume-chan/adb';
8
- import { Settings } from "./settings.js";
9
-
10
- export enum DemoModeSignalStrength {
11
- Hidden = 'null',
12
- Level0 = '0',
13
- Level1 = '1',
14
- Level2 = '2',
15
- Level3 = '3',
16
- Level4 = '4',
17
- }
18
-
19
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java;l=1073
20
- export const DemoModeMobileDataTypes = ['1x', '3g', '4g', '4g+', '5g', '5ge', '5g+',
21
- 'e', 'g', 'h', 'h+', 'lte', 'lte+', 'dis', 'not', 'null'] as const;
22
-
23
- export type DemoModeMobileDataType = (typeof DemoModeMobileDataTypes)[number];
24
-
25
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3136
26
- export const DemoModeStatusBarModes = ['opaque', 'translucent', 'semi-transparent', 'transparent', 'warning'] as const;
27
-
28
- export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
29
-
30
- export class DemoMode extends AdbCommandBase {
31
- private settings: Settings;
32
-
33
- constructor(adb: Adb) {
34
- super(adb);
35
- this.settings = new Settings(adb);
36
- }
37
-
38
- public static readonly AllowedSettingKey = 'sysui_demo_allowed';
39
-
40
- // Demo Mode actually doesn't have a setting indicates its enablement
41
- // However Developer Mode menu uses this key
42
- // So we can only try our best to guess if it's enabled
43
- public static readonly EnabledSettingKey = 'sysui_tuner_demo_on';
44
-
45
- public async getAllowed(): Promise<boolean> {
46
- const output = await this.settings.get('global', DemoMode.AllowedSettingKey);
47
- return output.trim() === '1';
48
- }
49
-
50
- public async setAllowed(value: boolean): Promise<void> {
51
- if (value) {
52
- await this.settings.put('global', DemoMode.AllowedSettingKey, '1');
53
- } else {
54
- await this.setEnabled(false);
55
- await this.settings.delete('global', DemoMode.AllowedSettingKey);
56
- }
57
- }
58
-
59
- public async getEnabled(): Promise<boolean> {
60
- const result = await this.settings.get('global', DemoMode.EnabledSettingKey);
61
- return result.trim() === '1';
62
- }
63
-
64
- public async setEnabled(value: boolean): Promise<void> {
65
- if (value) {
66
- await this.settings.put('global', DemoMode.EnabledSettingKey, '1');
67
- } else {
68
- await this.settings.delete('global', DemoMode.EnabledSettingKey);
69
- await this.broadcast('exit');
70
- }
71
- }
72
-
73
- public async broadcast(command: string, extra?: Record<string, string>): Promise<void> {
74
- await this.adb.subprocess.spawnAndWaitLegacy([
75
- 'am',
76
- 'broadcast',
77
- '-a',
78
- 'com.android.systemui.demo',
79
- '-e',
80
- 'command',
81
- command,
82
- ...(extra ? Object.entries(extra).flatMap(([key, value]) => ['-e', key, value]) : []),
83
- ]);
84
- }
85
-
86
- public async setBatteryLevel(level: number): Promise<void> {
87
- await this.broadcast('battery', { level: level.toString() });
88
- }
89
-
90
- public async setBatteryCharging(value: boolean): Promise<void> {
91
- await this.broadcast('battery', { plugged: value.toString() });
92
- }
93
-
94
- public async setPowerSaveMode(value: boolean): Promise<void> {
95
- await this.broadcast('battery', { powersave: value.toString() });
96
- }
97
-
98
- public async setAirplaneMode(show: boolean): Promise<void> {
99
- await this.broadcast('network', { airplane: show ? 'show' : 'hide' });
100
- }
101
-
102
- public async setWifiSignalStrength(value: DemoModeSignalStrength): Promise<void> {
103
- await this.broadcast('network', { wifi: 'show', level: value });
104
- }
105
-
106
- public async setMobileDataType(value: DemoModeMobileDataType): Promise<void> {
107
- for (let i = 0; i < 2; i += 1) {
108
- await this.broadcast('network', {
109
- mobile: 'show',
110
- sims: '1',
111
- nosim: 'hide',
112
- slot: '0',
113
- datatype: value,
114
- fully: 'true',
115
- roam: 'false',
116
- level: '4',
117
- inflate: 'false',
118
- activity: 'in',
119
- carriernetworkchange: 'hide',
120
- });
121
- }
122
- }
123
-
124
- public async setMobileSignalStrength(value: DemoModeSignalStrength): Promise<void> {
125
- await this.broadcast('network', { mobile: 'show', level: value });
126
- }
127
-
128
- public async setNoSimCardIcon(show: boolean): Promise<void> {
129
- await this.broadcast('network', { nosim: show ? 'show' : 'hide' });
130
- }
131
-
132
- public async setStatusBarMode(mode: DemoModeStatusBarMode): Promise<void> {
133
- await this.broadcast('bars', { mode });
134
- }
135
-
136
- public async setVibrateModeEnabled(value: boolean): Promise<void> {
137
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=103
138
- await this.broadcast('status', { volume: value ? 'vibrate' : 'hide' });
139
- }
140
-
141
- public async setBluetoothConnected(value: boolean): Promise<void> {
142
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=114
143
- await this.broadcast('status', { bluetooth: value ? 'connected' : 'hide' });
144
- }
145
-
146
- public async setLocatingIcon(show: boolean): Promise<void> {
147
- await this.broadcast('status', { location: show ? 'show' : 'hide' });
148
- }
149
-
150
- public async setAlarmIcon(show: boolean): Promise<void> {
151
- await this.broadcast('status', { alarm: show ? 'show' : 'hide' });
152
- }
153
-
154
- public async setSyncingIcon(show: boolean): Promise<void> {
155
- await this.broadcast('status', { sync: show ? 'show' : 'hide' });
156
- }
157
-
158
- public async setMuteIcon(show: boolean): Promise<void> {
159
- await this.broadcast('status', { mute: show ? 'show' : 'hide' });
160
- }
161
-
162
- public async setSpeakerPhoneIcon(show: boolean): Promise<void> {
163
- await this.broadcast('status', { speakerphone: show ? 'show' : 'hide' });
164
- }
165
-
166
- public async setNotificationsVisibility(show: boolean): Promise<void> {
167
- // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3131
168
- await this.broadcast('notifications', { visible: show.toString() });
169
- }
170
-
171
- public async setTime(hour: number, minute: number): Promise<void> {
172
- await this.broadcast('clock', {
173
- // cspell: disable-next-line
174
- hhmm:
175
- hour.toString().padStart(2, '0') +
176
- minute.toString().padStart(2, '0'),
177
- });
178
- }
179
- }
1
+ // cspell: ignore carriernetworkchange
2
+ // cspell: ignore powersave
3
+ // cspell: ignore nosim
4
+ // cspell: ignore systemui
5
+ // cspell: ignore sysui
6
+
7
+ import { Adb, AdbCommandBase } from '@yume-chan/adb';
8
+ import { Settings } from "./settings.js";
9
+
10
+ export enum DemoModeSignalStrength {
11
+ Hidden = 'null',
12
+ Level0 = '0',
13
+ Level1 = '1',
14
+ Level2 = '2',
15
+ Level3 = '3',
16
+ Level4 = '4',
17
+ }
18
+
19
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java;l=1362;drc=3b775bf7ad89902f94e03d191b0d8fbdebf2bdbf
20
+ export const DemoModeMobileDataTypes = ['1x', '3g', '4g', '4g+', '5g', '5ge', '5g+',
21
+ 'e', 'g', 'h', 'h+', 'lte', 'lte+', 'dis', 'not', 'null'] as const;
22
+
23
+ export type DemoModeMobileDataType = (typeof DemoModeMobileDataTypes)[number];
24
+
25
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3136
26
+ export const DemoModeStatusBarModes = ['opaque', 'translucent', 'semi-transparent', 'transparent', 'warning'] as const;
27
+
28
+ export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
29
+
30
+ export class DemoMode extends AdbCommandBase {
31
+ private settings: Settings;
32
+
33
+ constructor(adb: Adb) {
34
+ super(adb);
35
+ this.settings = new Settings(adb);
36
+ }
37
+
38
+ public static readonly AllowedSettingKey = 'sysui_demo_allowed';
39
+
40
+ // Demo Mode actually doesn't have a setting indicates its enablement
41
+ // However Developer Mode menu uses this key
42
+ // So we can only try our best to guess if it's enabled
43
+ public static readonly EnabledSettingKey = 'sysui_tuner_demo_on';
44
+
45
+ public async getAllowed(): Promise<boolean> {
46
+ const output = await this.settings.get('global', DemoMode.AllowedSettingKey);
47
+ return output.trim() === '1';
48
+ }
49
+
50
+ public async setAllowed(value: boolean): Promise<void> {
51
+ if (value) {
52
+ await this.settings.put('global', DemoMode.AllowedSettingKey, '1');
53
+ } else {
54
+ await this.setEnabled(false);
55
+ await this.settings.delete('global', DemoMode.AllowedSettingKey);
56
+ }
57
+ }
58
+
59
+ public async getEnabled(): Promise<boolean> {
60
+ const result = await this.settings.get('global', DemoMode.EnabledSettingKey);
61
+ return result.trim() === '1';
62
+ }
63
+
64
+ public async setEnabled(value: boolean): Promise<void> {
65
+ if (value) {
66
+ await this.settings.put('global', DemoMode.EnabledSettingKey, '1');
67
+ } else {
68
+ await this.settings.delete('global', DemoMode.EnabledSettingKey);
69
+ await this.broadcast('exit');
70
+ }
71
+ }
72
+
73
+ public async broadcast(command: string, extra?: Record<string, string>): Promise<void> {
74
+ await this.adb.subprocess.spawnAndWaitLegacy([
75
+ 'am',
76
+ 'broadcast',
77
+ '-a',
78
+ 'com.android.systemui.demo',
79
+ '-e',
80
+ 'command',
81
+ command,
82
+ ...(extra ? Object.entries(extra).flatMap(([key, value]) => ['-e', key, value]) : []),
83
+ ]);
84
+ }
85
+
86
+ public async setBatteryLevel(level: number): Promise<void> {
87
+ await this.broadcast('battery', { level: level.toString() });
88
+ }
89
+
90
+ public async setBatteryCharging(value: boolean): Promise<void> {
91
+ await this.broadcast('battery', { plugged: value.toString() });
92
+ }
93
+
94
+ public async setPowerSaveMode(value: boolean): Promise<void> {
95
+ await this.broadcast('battery', { powersave: value.toString() });
96
+ }
97
+
98
+ public async setAirplaneMode(show: boolean): Promise<void> {
99
+ await this.broadcast('network', { airplane: show ? 'show' : 'hide' });
100
+ }
101
+
102
+ public async setWifiSignalStrength(value: DemoModeSignalStrength): Promise<void> {
103
+ await this.broadcast('network', { wifi: 'show', level: value });
104
+ }
105
+
106
+ public async setMobileDataType(value: DemoModeMobileDataType): Promise<void> {
107
+ for (let i = 0; i < 2; i += 1) {
108
+ await this.broadcast('network', {
109
+ mobile: 'show',
110
+ sims: '1',
111
+ nosim: 'hide',
112
+ slot: '0',
113
+ datatype: value,
114
+ fully: 'true',
115
+ roam: 'false',
116
+ level: '4',
117
+ inflate: 'false',
118
+ activity: 'in',
119
+ carriernetworkchange: 'hide',
120
+ });
121
+ }
122
+ }
123
+
124
+ public async setMobileSignalStrength(value: DemoModeSignalStrength): Promise<void> {
125
+ await this.broadcast('network', { mobile: 'show', level: value });
126
+ }
127
+
128
+ public async setNoSimCardIcon(show: boolean): Promise<void> {
129
+ await this.broadcast('network', { nosim: show ? 'show' : 'hide' });
130
+ }
131
+
132
+ public async setStatusBarMode(mode: DemoModeStatusBarMode): Promise<void> {
133
+ await this.broadcast('bars', { mode });
134
+ }
135
+
136
+ public async setVibrateModeEnabled(value: boolean): Promise<void> {
137
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=103
138
+ await this.broadcast('status', { volume: value ? 'vibrate' : 'hide' });
139
+ }
140
+
141
+ public async setBluetoothConnected(value: boolean): Promise<void> {
142
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=114
143
+ await this.broadcast('status', { bluetooth: value ? 'connected' : 'hide' });
144
+ }
145
+
146
+ public async setLocatingIcon(show: boolean): Promise<void> {
147
+ await this.broadcast('status', { location: show ? 'show' : 'hide' });
148
+ }
149
+
150
+ public async setAlarmIcon(show: boolean): Promise<void> {
151
+ await this.broadcast('status', { alarm: show ? 'show' : 'hide' });
152
+ }
153
+
154
+ public async setSyncingIcon(show: boolean): Promise<void> {
155
+ await this.broadcast('status', { sync: show ? 'show' : 'hide' });
156
+ }
157
+
158
+ public async setMuteIcon(show: boolean): Promise<void> {
159
+ await this.broadcast('status', { mute: show ? 'show' : 'hide' });
160
+ }
161
+
162
+ public async setSpeakerPhoneIcon(show: boolean): Promise<void> {
163
+ await this.broadcast('status', { speakerphone: show ? 'show' : 'hide' });
164
+ }
165
+
166
+ public async setNotificationsVisibility(show: boolean): Promise<void> {
167
+ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3131
168
+ await this.broadcast('notifications', { visible: show.toString() });
169
+ }
170
+
171
+ public async setTime(hour: number, minute: number): Promise<void> {
172
+ await this.broadcast('clock', {
173
+ // cspell: disable-next-line
174
+ hhmm:
175
+ hour.toString().padStart(2, '0') +
176
+ minute.toString().padStart(2, '0'),
177
+ });
178
+ }
179
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- // cspell: ignore logcat
2
-
3
- export * from './bug-report.js';
4
- export * from './demo-mode.js';
5
- export * from './logcat.js';
6
- export * from './settings.js';
1
+ // cspell: ignore logcat
2
+
3
+ export * from './bug-report.js';
4
+ export * from './demo-mode.js';
5
+ export * from './logcat.js';
6
+ export * from './settings.js';
package/src/logcat.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  // cspell: ignore logcat
2
2
 
3
- import { AdbCommandBase, BufferedStream, BufferedStreamEndedError, DecodeUtf8Stream, ReadableStream, SplitLineStream, WritableStream } from "@yume-chan/adb";
4
- import Struct, { StructAsyncDeserializeStream } from "@yume-chan/struct";
3
+ import { AdbCommandBase, AdbSubprocessNoneProtocol } from '@yume-chan/adb';
4
+ import { BufferedTransformStream, DecodeUtf8Stream, SplitStringStream, WrapReadableStream, WritableStream, type ReadableStream } from '@yume-chan/stream-extra';
5
+ import Struct, { decodeUtf8, StructAsyncDeserializeStream } from '@yume-chan/struct';
5
6
 
6
7
  // `adb logcat` is an alias to `adb shell logcat`
7
8
  // so instead of adding to core library, it's implemented here
@@ -19,10 +20,12 @@ export enum LogId {
19
20
  Kernel,
20
21
  }
21
22
 
22
- export enum LogPriority {
23
+ // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=73;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
24
+ export enum AndroidLogPriority {
23
25
  Unknown,
24
26
  Default,
25
27
  Verbose,
28
+ Debug,
26
29
  Info,
27
30
  Warn,
28
31
  Error,
@@ -30,6 +33,41 @@ export enum LogPriority {
30
33
  Silent,
31
34
  }
32
35
 
36
+ // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
37
+ export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> = {
38
+ [AndroidLogPriority.Unknown]: '?',
39
+ [AndroidLogPriority.Default]: '?',
40
+ [AndroidLogPriority.Verbose]: 'V',
41
+ [AndroidLogPriority.Debug]: 'D',
42
+ [AndroidLogPriority.Info]: 'I',
43
+ [AndroidLogPriority.Warn]: 'W',
44
+ [AndroidLogPriority.Error]: 'E',
45
+ [AndroidLogPriority.Fatal]: 'F',
46
+ [AndroidLogPriority.Silent]: 'S',
47
+ };
48
+
49
+ export enum LogcatFormat {
50
+ Brief,
51
+ Process,
52
+ Tag,
53
+ Thread,
54
+ Raw,
55
+ Time,
56
+ ThreadTime,
57
+ Long
58
+ }
59
+
60
+ export interface LogcatFormatModifiers {
61
+ usec?: boolean;
62
+ printable?: boolean;
63
+ year?: boolean;
64
+ zone?: boolean;
65
+ epoch?: boolean;
66
+ monotonic?: boolean;
67
+ uid?: boolean;
68
+ descriptive?: boolean;
69
+ }
70
+
33
71
  export interface LogcatOptions {
34
72
  pid?: number;
35
73
  ids?: LogId[];
@@ -56,17 +94,60 @@ export const LoggerEntry =
56
94
 
57
95
  export type LoggerEntry = typeof LoggerEntry['TDeserializeResult'];
58
96
 
59
- export interface LogMessage extends LoggerEntry {
60
- priority: LogPriority;
61
- payload: Uint8Array;
97
+ // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;drc=bbe77d66e7bee8bd1f0bc7e5492b5376b0207ef6;bpv=0
98
+ export interface AndroidLogEntry extends LoggerEntry {
99
+ priority: AndroidLogPriority;
100
+ tag: string;
101
+ message: string;
102
+ }
103
+
104
+ // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
105
+ export function formatAndroidLogEntry(
106
+ entry: AndroidLogEntry,
107
+ format: LogcatFormat = LogcatFormat.Brief,
108
+ modifier?: LogcatFormatModifiers
109
+ ) {
110
+ const uid = modifier?.uid ? `${entry.uid.toString().padStart(5)}:` : '';
111
+
112
+ switch (format) {
113
+ // TODO: implement other formats
114
+ default:
115
+ return `${AndroidLogPriorityToCharacter[entry.priority]}/${entry.tag.padEnd(8)}(${uid}${entry.pid.toString().padStart(5)}): ${entry.message}`;
116
+ }
117
+ }
118
+
119
+ function findTagEnd(payload: Uint8Array) {
120
+ for (const separator of [0, ' '.charCodeAt(0), ':'.charCodeAt(0)]) {
121
+ const index = payload.indexOf(separator);
122
+ if (index !== -1) {
123
+ return index;
124
+ }
125
+ }
126
+
127
+ const index = payload.findIndex(x => x >= 0x7f);
128
+ if (index !== -1) {
129
+ return index;
130
+ }
131
+
132
+ return payload.length;
62
133
  }
63
134
 
64
- export async function deserializeLogMessage(stream: StructAsyncDeserializeStream): Promise<LogMessage> {
65
- const entry = await LoggerEntry.deserialize(stream);
66
- await stream.read(entry.headerSize - LoggerEntry.size);
67
- const priority = (await stream.read(1))[0] as LogPriority;
68
- const payload = await stream.read(entry.payloadSize - 1);
69
- return { ...entry, priority, payload };
135
+ export async function deserializeAndroidLogEntry(stream: StructAsyncDeserializeStream): Promise<AndroidLogEntry> {
136
+ const entry = await LoggerEntry.deserialize(stream) as unknown as AndroidLogEntry;
137
+ if (entry.headerSize !== LoggerEntry.size) {
138
+ await stream.read(entry.headerSize - LoggerEntry.size);
139
+ }
140
+ let payload = await stream.read(entry.payloadSize);
141
+
142
+ // https://cs.android.com/android/platform/superproject/+/master:system/logging/logcat/logcat.cpp;l=193-194;drc=bbe77d66e7bee8bd1f0bc7e5492b5376b0207ef6
143
+ // TODO: payload for some log IDs are in binary format.
144
+ entry.priority = payload[0] as AndroidLogPriority;
145
+
146
+ payload = payload.subarray(1);
147
+ const tagEnd = findTagEnd(payload);
148
+ entry.tag = decodeUtf8(payload.subarray(0, tagEnd));
149
+ entry.message = tagEnd < payload.length - 1 ? decodeUtf8(payload.subarray(tagEnd + 1)) : '';
150
+ return entry;
70
151
  }
71
152
 
72
153
  export interface LogSize {
@@ -115,7 +196,7 @@ export class Logcat extends AdbCommandBase {
115
196
  const result: LogSize[] = [];
116
197
  await stdout
117
198
  .pipeThrough(new DecodeUtf8Stream())
118
- .pipeThrough(new SplitLineStream())
199
+ .pipeThrough(new SplitStringStream('\n'))
119
200
  .pipeTo(new WritableStream({
120
201
  write(chunk) {
121
202
  let match = chunk.match(Logcat.LOG_SIZE_REGEX_11);
@@ -154,34 +235,22 @@ export class Logcat extends AdbCommandBase {
154
235
  ]);
155
236
  }
156
237
 
157
- public binary(options?: LogcatOptions): ReadableStream<LogMessage> {
158
- let bufferedStream: BufferedStream;
159
- return new ReadableStream({
160
- start: async () => {
161
- const { stdout } = await this.adb.subprocess.spawn([
162
- 'logcat',
163
- '-B',
164
- ...(options?.pid ? ['--pid', options.pid.toString()] : []),
165
- ...(options?.ids ? ['-b', Logcat.joinLogId(options.ids)] : [])
166
- ]);
167
- bufferedStream = new BufferedStream(stdout);
168
- },
169
- async pull(controller) {
170
- try {
171
- const entry = await deserializeLogMessage(bufferedStream);
172
- controller.enqueue(entry);
173
- } catch (e) {
174
- if (e instanceof BufferedStreamEndedError) {
175
- controller.close();
176
- return;
177
- }
178
-
179
- throw e;
180
- }
181
- },
182
- cancel() {
183
- bufferedStream.close();
184
- },
185
- });
238
+ public binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
239
+ return new WrapReadableStream(async () => {
240
+ // TODO: make `spawn` return synchronously with streams pending
241
+ // so it's easier to chain them.
242
+ const { stdout } = await this.adb.subprocess.spawn([
243
+ 'logcat',
244
+ '-B',
245
+ ...(options?.pid ? ['--pid', options.pid.toString()] : []),
246
+ ...(options?.ids ? ['-b', Logcat.joinLogId(options.ids)] : [])
247
+ ], {
248
+ // PERF: None protocol is 150% faster then Shell protocol
249
+ protocols: [AdbSubprocessNoneProtocol],
250
+ });
251
+ return stdout;
252
+ }).pipeThrough(new BufferedTransformStream(stream => {
253
+ return deserializeAndroidLogEntry(stream);
254
+ }));
186
255
  }
187
256
  }