@yume-chan/android-bin 0.0.0-20240714132542
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 +129 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/esm/am.d.ts +17 -0
- package/esm/am.d.ts.map +1 -0
- package/esm/am.js +42 -0
- package/esm/am.js.map +1 -0
- package/esm/bu.d.ts +28 -0
- package/esm/bu.d.ts.map +1 -0
- package/esm/bu.js +53 -0
- package/esm/bu.js.map +1 -0
- package/esm/bug-report.d.ts +99 -0
- package/esm/bug-report.d.ts.map +1 -0
- package/esm/bug-report.js +271 -0
- package/esm/bug-report.js.map +1 -0
- package/esm/cmd.d.ts +25 -0
- package/esm/cmd.d.ts.map +1 -0
- package/esm/cmd.js +81 -0
- package/esm/cmd.js.map +1 -0
- package/esm/demo-mode.d.ts +44 -0
- package/esm/demo-mode.d.ts.map +1 -0
- package/esm/demo-mode.js +179 -0
- package/esm/demo-mode.js.map +1 -0
- package/esm/dumpsys.d.ts +51 -0
- package/esm/dumpsys.d.ts.map +1 -0
- package/esm/dumpsys.js +114 -0
- package/esm/dumpsys.js.map +1 -0
- package/esm/index.d.ts +13 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +14 -0
- package/esm/index.js.map +1 -0
- package/esm/intent.d.ts +11 -0
- package/esm/intent.d.ts.map +1 -0
- package/esm/intent.js +59 -0
- package/esm/intent.js.map +1 -0
- package/esm/logcat.d.ts +93 -0
- package/esm/logcat.d.ts.map +1 -0
- package/esm/logcat.js +300 -0
- package/esm/logcat.js.map +1 -0
- package/esm/overlay-display.d.ts +38 -0
- package/esm/overlay-display.d.ts.map +1 -0
- package/esm/overlay-display.js +51 -0
- package/esm/overlay-display.js.map +1 -0
- package/esm/pm.d.ts +211 -0
- package/esm/pm.d.ts.map +1 -0
- package/esm/pm.js +382 -0
- package/esm/pm.js.map +1 -0
- package/esm/settings.d.ts +27 -0
- package/esm/settings.d.ts.map +1 -0
- package/esm/settings.js +66 -0
- package/esm/settings.js.map +1 -0
- package/esm/string-format.d.ts +43 -0
- package/esm/string-format.d.ts.map +1 -0
- package/esm/string-format.js +152 -0
- package/esm/string-format.js.map +1 -0
- package/esm/utils.d.ts +4 -0
- package/esm/utils.d.ts.map +1 -0
- package/esm/utils.js +23 -0
- package/esm/utils.js.map +1 -0
- package/package.json +49 -0
- package/src/am.ts +71 -0
- package/src/bu.ts +80 -0
- package/src/bug-report.ts +348 -0
- package/src/cmd.ts +113 -0
- package/src/demo-mode.ts +232 -0
- package/src/dumpsys.ts +153 -0
- package/src/index.ts +14 -0
- package/src/intent.ts +73 -0
- package/src/logcat.ts +509 -0
- package/src/overlay-display.ts +112 -0
- package/src/pm.ts +674 -0
- package/src/settings.ts +128 -0
- package/src/string-format.ts +197 -0
- package/src/utils.ts +29 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.build.tsbuildinfo +1 -0
package/src/logcat.ts
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
// cspell: ignore logcat
|
|
2
|
+
// cspell: ignore usec
|
|
3
|
+
|
|
4
|
+
import { AdbCommandBase, AdbSubprocessNoneProtocol } from "@yume-chan/adb";
|
|
5
|
+
import type { ReadableStream } from "@yume-chan/stream-extra";
|
|
6
|
+
import {
|
|
7
|
+
BufferedTransformStream,
|
|
8
|
+
SplitStringStream,
|
|
9
|
+
TextDecoderStream,
|
|
10
|
+
WrapReadableStream,
|
|
11
|
+
WritableStream,
|
|
12
|
+
} from "@yume-chan/stream-extra";
|
|
13
|
+
import type { AsyncExactReadable } from "@yume-chan/struct";
|
|
14
|
+
import Struct, { decodeUtf8 } from "@yume-chan/struct";
|
|
15
|
+
|
|
16
|
+
// `adb logcat` is an alias to `adb shell logcat`
|
|
17
|
+
// so instead of adding to core library, it's implemented here
|
|
18
|
+
|
|
19
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=141;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
|
|
20
|
+
export enum LogId {
|
|
21
|
+
All = -1,
|
|
22
|
+
Main,
|
|
23
|
+
Radio,
|
|
24
|
+
Events,
|
|
25
|
+
System,
|
|
26
|
+
Crash,
|
|
27
|
+
Stats,
|
|
28
|
+
Security,
|
|
29
|
+
Kernel,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/android/log.h;l=73;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
|
|
33
|
+
export enum AndroidLogPriority {
|
|
34
|
+
Unknown,
|
|
35
|
+
Default,
|
|
36
|
+
Verbose,
|
|
37
|
+
Debug,
|
|
38
|
+
Info,
|
|
39
|
+
Warn,
|
|
40
|
+
Error,
|
|
41
|
+
Fatal,
|
|
42
|
+
Silent,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=140;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
|
|
46
|
+
export const AndroidLogPriorityToCharacter: Record<AndroidLogPriority, string> =
|
|
47
|
+
{
|
|
48
|
+
[AndroidLogPriority.Unknown]: "?",
|
|
49
|
+
[AndroidLogPriority.Default]: "?",
|
|
50
|
+
[AndroidLogPriority.Verbose]: "V",
|
|
51
|
+
[AndroidLogPriority.Debug]: "D",
|
|
52
|
+
[AndroidLogPriority.Info]: "I",
|
|
53
|
+
[AndroidLogPriority.Warn]: "W",
|
|
54
|
+
[AndroidLogPriority.Error]: "E",
|
|
55
|
+
[AndroidLogPriority.Fatal]: "F",
|
|
56
|
+
[AndroidLogPriority.Silent]: "S",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export enum LogcatFormat {
|
|
60
|
+
Brief,
|
|
61
|
+
Process,
|
|
62
|
+
Tag,
|
|
63
|
+
Thread,
|
|
64
|
+
Raw,
|
|
65
|
+
Time,
|
|
66
|
+
ThreadTime,
|
|
67
|
+
Long,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface LogcatFormatModifiers {
|
|
71
|
+
microseconds?: boolean;
|
|
72
|
+
nanoseconds?: boolean;
|
|
73
|
+
printable?: boolean;
|
|
74
|
+
year?: boolean;
|
|
75
|
+
timezone?: boolean;
|
|
76
|
+
epoch?: boolean;
|
|
77
|
+
monotonic?: boolean;
|
|
78
|
+
uid?: boolean;
|
|
79
|
+
descriptive?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface LogcatOptions {
|
|
83
|
+
dump?: boolean;
|
|
84
|
+
pid?: number;
|
|
85
|
+
ids?: LogId[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const NANOSECONDS_PER_SECOND = BigInt(1e9);
|
|
89
|
+
|
|
90
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/include/log/log_read.h;l=39;drc=82b5738732161dbaafb2e2f25cce19cd26b9157d
|
|
91
|
+
export const LoggerEntry = new Struct({ littleEndian: true })
|
|
92
|
+
.uint16("payloadSize")
|
|
93
|
+
.uint16("headerSize")
|
|
94
|
+
.int32("pid")
|
|
95
|
+
.uint32("tid")
|
|
96
|
+
.uint32("seconds")
|
|
97
|
+
.uint32("nanoseconds")
|
|
98
|
+
.uint32("logId")
|
|
99
|
+
.uint32("uid")
|
|
100
|
+
.extra({
|
|
101
|
+
get timestamp() {
|
|
102
|
+
return (
|
|
103
|
+
BigInt(this.seconds) * NANOSECONDS_PER_SECOND +
|
|
104
|
+
BigInt(this.nanoseconds)
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export type LoggerEntry = (typeof LoggerEntry)["TDeserializeResult"];
|
|
110
|
+
|
|
111
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;drc=bbe77d66e7bee8bd1f0bc7e5492b5376b0207ef6;bpv=0
|
|
112
|
+
export interface AndroidLogEntry extends LoggerEntry {
|
|
113
|
+
priority: AndroidLogPriority;
|
|
114
|
+
tag: string;
|
|
115
|
+
message: string;
|
|
116
|
+
|
|
117
|
+
toString(format?: LogcatFormat, modifiers?: LogcatFormatModifiers): string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function padZero(number: number, length: number) {
|
|
121
|
+
return number.toString().padStart(length, "0");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function formatSeconds(seconds: number, modifiers: LogcatFormatModifiers) {
|
|
125
|
+
if (modifiers.monotonic) {
|
|
126
|
+
return padZero(seconds, 6);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (modifiers.epoch) {
|
|
130
|
+
return padZero(seconds, 19);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const date = new Date(seconds * 1000);
|
|
134
|
+
|
|
135
|
+
const month = padZero(date.getMonth() + 1, 2);
|
|
136
|
+
const day = padZero(date.getDate(), 2);
|
|
137
|
+
const hour = padZero(date.getHours(), 2);
|
|
138
|
+
const minute = padZero(date.getMinutes(), 2);
|
|
139
|
+
const second = padZero(date.getSeconds(), 2);
|
|
140
|
+
const result = `${month}-${day} ${hour}:${minute}:${second}`;
|
|
141
|
+
|
|
142
|
+
if (modifiers.year) {
|
|
143
|
+
const year = padZero(date.getFullYear(), 4);
|
|
144
|
+
return `${year}-${result}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatNanoseconds(
|
|
151
|
+
nanoseconds: number,
|
|
152
|
+
modifiers: LogcatFormatModifiers,
|
|
153
|
+
) {
|
|
154
|
+
if (modifiers.nanoseconds) {
|
|
155
|
+
return padZero(nanoseconds, 9);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (modifiers.microseconds) {
|
|
159
|
+
return padZero(nanoseconds / 1000, 6);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return padZero(nanoseconds / 1000000, 3);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function formatTimezone(seconds: number, modifiers: LogcatFormatModifiers) {
|
|
166
|
+
if (!modifiers.timezone || modifiers.monotonic || modifiers.epoch) {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const date = new Date(seconds * 1000);
|
|
171
|
+
const offset = date.getTimezoneOffset();
|
|
172
|
+
const sign = offset <= 0 ? "+" : "-";
|
|
173
|
+
const absolute = Math.abs(offset);
|
|
174
|
+
const hours = (absolute / 60) | 0;
|
|
175
|
+
const minutes = absolute % 60;
|
|
176
|
+
|
|
177
|
+
// prettier-ignore
|
|
178
|
+
return ` ${
|
|
179
|
+
sign
|
|
180
|
+
}${
|
|
181
|
+
hours.toString().padStart(2, "0")
|
|
182
|
+
}:${
|
|
183
|
+
minutes.toString().padStart(2, "0")
|
|
184
|
+
}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function formatTime(
|
|
188
|
+
seconds: number,
|
|
189
|
+
nanoseconds: number,
|
|
190
|
+
modifiers: LogcatFormatModifiers,
|
|
191
|
+
) {
|
|
192
|
+
const secondsString = formatSeconds(seconds, modifiers);
|
|
193
|
+
const nanosecondsString = formatNanoseconds(nanoseconds, modifiers);
|
|
194
|
+
const zoneString = formatTimezone(seconds, modifiers);
|
|
195
|
+
return `${secondsString}.${nanosecondsString}${zoneString}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function formatUid(
|
|
199
|
+
uid: number,
|
|
200
|
+
modifiers: LogcatFormatModifiers,
|
|
201
|
+
suffix: string,
|
|
202
|
+
) {
|
|
203
|
+
return modifiers.uid ? `${uid.toString().padStart(5)}${suffix}` : "";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getFormatPrefix(
|
|
207
|
+
entry: AndroidLogEntry,
|
|
208
|
+
format: LogcatFormat,
|
|
209
|
+
modifiers: LogcatFormatModifiers,
|
|
210
|
+
) {
|
|
211
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
|
|
212
|
+
switch (format) {
|
|
213
|
+
// TODO: implement other formats
|
|
214
|
+
case LogcatFormat.Tag:
|
|
215
|
+
// prettier-ignore
|
|
216
|
+
return `${
|
|
217
|
+
AndroidLogPriorityToCharacter[entry.priority]
|
|
218
|
+
}/${
|
|
219
|
+
entry.tag.padEnd(8)
|
|
220
|
+
}: `;
|
|
221
|
+
case LogcatFormat.Process:
|
|
222
|
+
// prettier-ignore
|
|
223
|
+
return `${
|
|
224
|
+
AndroidLogPriorityToCharacter[entry.priority]
|
|
225
|
+
}(${
|
|
226
|
+
formatUid(entry.uid, modifiers, ":")
|
|
227
|
+
}${
|
|
228
|
+
entry.pid.toString().padStart(5)
|
|
229
|
+
}) `;
|
|
230
|
+
case LogcatFormat.Thread:
|
|
231
|
+
// prettier-ignore
|
|
232
|
+
return `${
|
|
233
|
+
AndroidLogPriorityToCharacter[entry.priority]
|
|
234
|
+
}(${
|
|
235
|
+
formatUid(entry.uid, modifiers, ":")
|
|
236
|
+
}${
|
|
237
|
+
entry.pid.toString().padStart(5)
|
|
238
|
+
}:${
|
|
239
|
+
entry.tid.toString().padStart(5)
|
|
240
|
+
}) `;
|
|
241
|
+
case LogcatFormat.Raw:
|
|
242
|
+
return "";
|
|
243
|
+
case LogcatFormat.Time:
|
|
244
|
+
// prettier-ignore
|
|
245
|
+
return `${
|
|
246
|
+
formatTime(entry.seconds, entry.nanoseconds, modifiers)
|
|
247
|
+
} ${
|
|
248
|
+
AndroidLogPriorityToCharacter[entry.priority]
|
|
249
|
+
}/${
|
|
250
|
+
entry.tag.padEnd(8)
|
|
251
|
+
}(${
|
|
252
|
+
formatUid(entry.uid, modifiers, ":")
|
|
253
|
+
}${
|
|
254
|
+
entry.pid.toString().padStart(5)
|
|
255
|
+
}): `;
|
|
256
|
+
case LogcatFormat.ThreadTime:
|
|
257
|
+
// prettier-ignore
|
|
258
|
+
return `${
|
|
259
|
+
formatTime(entry.seconds, entry.nanoseconds, modifiers)
|
|
260
|
+
} ${
|
|
261
|
+
formatUid(entry.uid, modifiers, " ")
|
|
262
|
+
}${
|
|
263
|
+
entry.pid.toString().padStart(5)
|
|
264
|
+
} ${
|
|
265
|
+
entry.tid.toString().padStart(5)
|
|
266
|
+
} ${
|
|
267
|
+
AndroidLogPriorityToCharacter[entry.priority]
|
|
268
|
+
} ${
|
|
269
|
+
entry.tag.toString().padEnd(8)
|
|
270
|
+
}: `;
|
|
271
|
+
case LogcatFormat.Brief:
|
|
272
|
+
default:
|
|
273
|
+
// prettier-ignore
|
|
274
|
+
return `${
|
|
275
|
+
AndroidLogPriorityToCharacter[entry.priority]
|
|
276
|
+
}/${
|
|
277
|
+
entry.tag.padEnd(8)
|
|
278
|
+
}(${
|
|
279
|
+
formatUid(entry.uid, modifiers, ":")
|
|
280
|
+
}${
|
|
281
|
+
entry.pid.toString().padStart(5)
|
|
282
|
+
}): `;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getFormatSuffix(entry: AndroidLogEntry, format: LogcatFormat) {
|
|
287
|
+
switch (format) {
|
|
288
|
+
case LogcatFormat.Process:
|
|
289
|
+
return ` (${entry.tag})`;
|
|
290
|
+
default:
|
|
291
|
+
return "";
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function formatEntryWrapLine(
|
|
296
|
+
entry: AndroidLogEntry,
|
|
297
|
+
format: LogcatFormat,
|
|
298
|
+
modifiers: LogcatFormatModifiers,
|
|
299
|
+
) {
|
|
300
|
+
const prefix = getFormatPrefix(entry, format, modifiers);
|
|
301
|
+
const suffix = getFormatSuffix(entry, format);
|
|
302
|
+
return (
|
|
303
|
+
prefix + entry.message.replaceAll("\n", suffix + "\n" + prefix) + suffix
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function AndroidLogEntryToString(
|
|
308
|
+
this: AndroidLogEntry,
|
|
309
|
+
format: LogcatFormat = LogcatFormat.ThreadTime,
|
|
310
|
+
modifiers: LogcatFormatModifiers = {},
|
|
311
|
+
) {
|
|
312
|
+
switch (format) {
|
|
313
|
+
case LogcatFormat.Long:
|
|
314
|
+
// prettier-ignore
|
|
315
|
+
return `[ ${
|
|
316
|
+
formatTime(this.seconds, this.nanoseconds, modifiers)
|
|
317
|
+
} ${
|
|
318
|
+
formatUid(this.uid, modifiers, ":")
|
|
319
|
+
}${
|
|
320
|
+
this.pid.toString().padStart(5)
|
|
321
|
+
}:${
|
|
322
|
+
this.tid.toString().padStart(5)
|
|
323
|
+
} ${
|
|
324
|
+
AndroidLogPriorityToCharacter[this.priority]
|
|
325
|
+
}/${
|
|
326
|
+
this.tag.padEnd(8)
|
|
327
|
+
} ]\n${
|
|
328
|
+
this.message
|
|
329
|
+
}\n`;
|
|
330
|
+
default:
|
|
331
|
+
return formatEntryWrapLine(this, format, modifiers);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function findTagEnd(payload: Uint8Array) {
|
|
336
|
+
for (const separator of [0, " ".charCodeAt(0), ":".charCodeAt(0)]) {
|
|
337
|
+
const index = payload.indexOf(separator);
|
|
338
|
+
if (index !== -1) {
|
|
339
|
+
return index;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const index = payload.findIndex((x) => x >= 0x7f);
|
|
344
|
+
if (index !== -1) {
|
|
345
|
+
return index;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return payload.length;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export async function deserializeAndroidLogEntry(
|
|
352
|
+
stream: AsyncExactReadable,
|
|
353
|
+
): Promise<AndroidLogEntry> {
|
|
354
|
+
const entry = (await LoggerEntry.deserialize(stream)) as AndroidLogEntry;
|
|
355
|
+
if (entry.headerSize !== LoggerEntry.size) {
|
|
356
|
+
// Skip unknown fields
|
|
357
|
+
await stream.readExactly(entry.headerSize - LoggerEntry.size);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let payload = await stream.readExactly(entry.payloadSize);
|
|
361
|
+
|
|
362
|
+
// https://cs.android.com/android/platform/superproject/+/master:system/logging/logcat/logcat.cpp;l=193-194;drc=bbe77d66e7bee8bd1f0bc7e5492b5376b0207ef6
|
|
363
|
+
// TODO: payload for some log IDs are in binary format.
|
|
364
|
+
entry.priority = payload[0] as AndroidLogPriority;
|
|
365
|
+
|
|
366
|
+
payload = payload.subarray(1);
|
|
367
|
+
const tagEnd = findTagEnd(payload);
|
|
368
|
+
entry.tag = decodeUtf8(payload.subarray(0, tagEnd));
|
|
369
|
+
entry.message =
|
|
370
|
+
tagEnd < payload.length - 1
|
|
371
|
+
? decodeUtf8(payload.subarray(tagEnd + 1))
|
|
372
|
+
: "";
|
|
373
|
+
entry.toString = AndroidLogEntryToString;
|
|
374
|
+
return entry;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export interface LogSize {
|
|
378
|
+
id: LogId;
|
|
379
|
+
size: number;
|
|
380
|
+
readable?: number;
|
|
381
|
+
consumed: number;
|
|
382
|
+
maxEntrySize: number;
|
|
383
|
+
maxPayloadSize: number;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export class Logcat extends AdbCommandBase {
|
|
387
|
+
static logIdToName(id: LogId): string {
|
|
388
|
+
return LogId[id];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
static logNameToId(name: string): LogId {
|
|
392
|
+
const key = name[0]!.toUpperCase() + name.substring(1);
|
|
393
|
+
return LogId[key as keyof typeof LogId];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static joinLogId(ids: LogId[]): string {
|
|
397
|
+
return ids.map((id) => Logcat.logIdToName(id)).join(",");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
static parseSize(value: number, multiplier: string): number {
|
|
401
|
+
const MULTIPLIERS = ["", "Ki", "Mi", "Gi"];
|
|
402
|
+
return value * 1024 ** (MULTIPLIERS.indexOf(multiplier) || 0);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// TODO: logcat: Support output format before Android 10
|
|
406
|
+
// ref https://android-review.googlesource.com/c/platform/system/core/+/748128
|
|
407
|
+
static readonly LOG_SIZE_REGEX_10: RegExp =
|
|
408
|
+
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed\), max entry is (.*) B, max payload is (.*) B/;
|
|
409
|
+
|
|
410
|
+
// Android 11 added `readable` part
|
|
411
|
+
// ref https://android-review.googlesource.com/c/platform/system/core/+/1390940
|
|
412
|
+
static readonly LOG_SIZE_REGEX_11: RegExp =
|
|
413
|
+
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed, (.*) (.*)B readable\), max entry is (.*) B, max payload is (.*) B/;
|
|
414
|
+
|
|
415
|
+
async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
|
|
416
|
+
const { stdout } = await this.adb.subprocess.spawn([
|
|
417
|
+
"logcat",
|
|
418
|
+
"-g",
|
|
419
|
+
...(ids ? ["-b", Logcat.joinLogId(ids)] : []),
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
const result: LogSize[] = [];
|
|
423
|
+
await stdout
|
|
424
|
+
.pipeThrough(new TextDecoderStream())
|
|
425
|
+
.pipeThrough(new SplitStringStream("\n"))
|
|
426
|
+
.pipeTo(
|
|
427
|
+
new WritableStream({
|
|
428
|
+
write(chunk) {
|
|
429
|
+
let match = chunk.match(Logcat.LOG_SIZE_REGEX_11);
|
|
430
|
+
if (match) {
|
|
431
|
+
result.push({
|
|
432
|
+
id: Logcat.logNameToId(match[1]!),
|
|
433
|
+
size: Logcat.parseSize(
|
|
434
|
+
Number.parseInt(match[2]!, 10),
|
|
435
|
+
match[3]!,
|
|
436
|
+
),
|
|
437
|
+
readable: Logcat.parseSize(
|
|
438
|
+
Number.parseInt(match[6]!, 10),
|
|
439
|
+
match[7]!,
|
|
440
|
+
),
|
|
441
|
+
consumed: Logcat.parseSize(
|
|
442
|
+
Number.parseInt(match[4]!, 10),
|
|
443
|
+
match[5]!,
|
|
444
|
+
),
|
|
445
|
+
maxEntrySize: parseInt(match[8]!, 10),
|
|
446
|
+
maxPayloadSize: parseInt(match[9]!, 10),
|
|
447
|
+
});
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
match = chunk.match(Logcat.LOG_SIZE_REGEX_10);
|
|
452
|
+
if (match) {
|
|
453
|
+
result.push({
|
|
454
|
+
id: Logcat.logNameToId(match[1]!),
|
|
455
|
+
size: Logcat.parseSize(
|
|
456
|
+
Number.parseInt(match[2]!, 10),
|
|
457
|
+
match[3]!,
|
|
458
|
+
),
|
|
459
|
+
consumed: Logcat.parseSize(
|
|
460
|
+
Number.parseInt(match[4]!, 10),
|
|
461
|
+
match[5]!,
|
|
462
|
+
),
|
|
463
|
+
maxEntrySize: parseInt(match[6]!, 10),
|
|
464
|
+
maxPayloadSize: parseInt(match[7]!, 10),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
}),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async clear(ids?: LogId[]): Promise<void> {
|
|
475
|
+
const args = ["logcat", "-c"];
|
|
476
|
+
if (ids && ids.length > 0) {
|
|
477
|
+
args.push("-b", Logcat.joinLogId(ids));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await this.adb.subprocess.spawnAndWait(args);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
|
|
484
|
+
return new WrapReadableStream(async () => {
|
|
485
|
+
const args = ["logcat", "-B"];
|
|
486
|
+
if (options?.dump) {
|
|
487
|
+
args.push("-d");
|
|
488
|
+
}
|
|
489
|
+
if (options?.pid) {
|
|
490
|
+
args.push("--pid", options.pid.toString());
|
|
491
|
+
}
|
|
492
|
+
if (options?.ids) {
|
|
493
|
+
args.push("-b", Logcat.joinLogId(options.ids));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// TODO: make `spawn` return synchronously with streams pending
|
|
497
|
+
// so it's easier to chain them.
|
|
498
|
+
const { stdout } = await this.adb.subprocess.spawn(args, {
|
|
499
|
+
// PERF: None protocol is 150% faster then Shell protocol
|
|
500
|
+
protocols: [AdbSubprocessNoneProtocol],
|
|
501
|
+
});
|
|
502
|
+
return stdout;
|
|
503
|
+
}).pipeThrough(
|
|
504
|
+
new BufferedTransformStream((stream) => {
|
|
505
|
+
return deserializeAndroidLogEntry(stream);
|
|
506
|
+
}),
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Adb } from "@yume-chan/adb";
|
|
2
|
+
import { AdbCommandBase } from "@yume-chan/adb";
|
|
3
|
+
|
|
4
|
+
import { Settings } from "./settings.js";
|
|
5
|
+
import { p } from "./string-format.js";
|
|
6
|
+
|
|
7
|
+
export interface OverlayDisplayDeviceMode {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
density: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface OverlayDisplayDevice {
|
|
14
|
+
modes: OverlayDisplayDeviceMode[];
|
|
15
|
+
secure: boolean;
|
|
16
|
+
ownContentOnly: boolean;
|
|
17
|
+
showSystemDecorations: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class OverlayDisplay extends AdbCommandBase {
|
|
21
|
+
#settings: Settings;
|
|
22
|
+
|
|
23
|
+
static readonly SETTING_KEY = "overlay_display_devices";
|
|
24
|
+
|
|
25
|
+
static readonly SETTING_FORMAT = p.separated(
|
|
26
|
+
";",
|
|
27
|
+
p.sequence(
|
|
28
|
+
{
|
|
29
|
+
name: "modes",
|
|
30
|
+
format: p.separated(
|
|
31
|
+
"|",
|
|
32
|
+
p.sequence(
|
|
33
|
+
{ name: "width", format: p.digits() },
|
|
34
|
+
p.literal("x"),
|
|
35
|
+
{ name: "height", format: p.digits() },
|
|
36
|
+
p.literal("/"),
|
|
37
|
+
{ name: "density", format: p.digits() },
|
|
38
|
+
),
|
|
39
|
+
{ min: 1 },
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "flags",
|
|
44
|
+
format: p.map(
|
|
45
|
+
p.repeated(
|
|
46
|
+
p.sequence(p.literal(","), {
|
|
47
|
+
name: "flag",
|
|
48
|
+
format: p.union(
|
|
49
|
+
p.literal("secure"),
|
|
50
|
+
p.literal("own_content_only"),
|
|
51
|
+
p.literal("show_system_decorations"),
|
|
52
|
+
),
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
(value) => value.map((item) => item.flag),
|
|
56
|
+
(value) => value.map((item) => ({ flag: item })),
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
constructor(adb: Adb) {
|
|
63
|
+
super(adb);
|
|
64
|
+
this.#settings = new Settings(adb);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async get() {
|
|
68
|
+
return OverlayDisplay.SETTING_FORMAT.parse({
|
|
69
|
+
value: await this.#settings.get(
|
|
70
|
+
"global",
|
|
71
|
+
OverlayDisplay.SETTING_KEY,
|
|
72
|
+
),
|
|
73
|
+
position: 0,
|
|
74
|
+
}).map((device) => ({
|
|
75
|
+
modes: device.modes,
|
|
76
|
+
secure: device.flags.includes("secure"),
|
|
77
|
+
ownContentOnly: device.flags.includes("own_content_only"),
|
|
78
|
+
showSystemDecorations: device.flags.includes(
|
|
79
|
+
"show_system_decorations",
|
|
80
|
+
),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async set(devices: OverlayDisplayDevice[]): Promise<void> {
|
|
85
|
+
await this.#settings.put(
|
|
86
|
+
"global",
|
|
87
|
+
OverlayDisplay.SETTING_KEY,
|
|
88
|
+
OverlayDisplay.SETTING_FORMAT.stringify(
|
|
89
|
+
devices.map((device) => {
|
|
90
|
+
const flags: (
|
|
91
|
+
| "secure"
|
|
92
|
+
| "own_content_only"
|
|
93
|
+
| "show_system_decorations"
|
|
94
|
+
)[] = [];
|
|
95
|
+
if (device.secure) {
|
|
96
|
+
flags.push("secure");
|
|
97
|
+
}
|
|
98
|
+
if (device.ownContentOnly) {
|
|
99
|
+
flags.push("own_content_only");
|
|
100
|
+
}
|
|
101
|
+
if (device.showSystemDecorations) {
|
|
102
|
+
flags.push("show_system_decorations");
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
modes: device.modes,
|
|
106
|
+
flags,
|
|
107
|
+
};
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|