@yume-chan/android-bin 0.0.18 → 0.0.20

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.
Files changed (51) hide show
  1. package/CHANGELOG.json +21 -0
  2. package/CHANGELOG.md +14 -1
  3. package/esm/bu.d.ts +14 -14
  4. package/esm/bu.js +13 -13
  5. package/esm/bug-report.d.ts +37 -37
  6. package/esm/bug-report.d.ts.map +1 -1
  7. package/esm/bug-report.js +136 -135
  8. package/esm/bug-report.js.map +1 -1
  9. package/esm/cmd.d.ts +15 -0
  10. package/esm/cmd.d.ts.map +1 -0
  11. package/esm/cmd.js +54 -0
  12. package/esm/cmd.js.map +1 -0
  13. package/esm/demo-mode.d.ts +43 -42
  14. package/esm/demo-mode.d.ts.map +1 -1
  15. package/esm/demo-mode.js +179 -178
  16. package/esm/demo-mode.js.map +1 -1
  17. package/esm/dumpsys.d.ts +4 -0
  18. package/esm/dumpsys.d.ts.map +1 -0
  19. package/esm/dumpsys.js +4 -0
  20. package/esm/dumpsys.js.map +1 -0
  21. package/esm/index.d.ts +6 -4
  22. package/esm/index.d.ts.map +1 -1
  23. package/esm/index.js +7 -5
  24. package/esm/index.js.map +1 -1
  25. package/esm/logcat.d.ts +91 -89
  26. package/esm/logcat.d.ts.map +1 -1
  27. package/esm/logcat.js +296 -200
  28. package/esm/logcat.js.map +1 -1
  29. package/esm/overlay-display.d.ts +21 -0
  30. package/esm/overlay-display.d.ts.map +1 -0
  31. package/esm/overlay-display.js +78 -0
  32. package/esm/overlay-display.js.map +1 -0
  33. package/esm/pm.d.ts +139 -0
  34. package/esm/pm.d.ts.map +1 -0
  35. package/esm/pm.js +125 -0
  36. package/esm/pm.js.map +1 -0
  37. package/esm/settings.d.ts +12 -12
  38. package/esm/settings.d.ts.map +1 -1
  39. package/esm/settings.js +40 -24
  40. package/esm/settings.js.map +1 -1
  41. package/package.json +11 -10
  42. package/src/bug-report.ts +3 -3
  43. package/src/cmd.ts +67 -0
  44. package/src/demo-mode.ts +4 -3
  45. package/src/index.ts +6 -4
  46. package/src/logcat.ts +224 -25
  47. package/src/overlay-display.ts +115 -0
  48. package/src/pm.ts +281 -0
  49. package/src/settings.ts +54 -26
  50. package/tsconfig.build.json +11 -0
  51. package/tsconfig.build.tsbuildinfo +1 -1
package/src/logcat.ts CHANGED
@@ -1,18 +1,17 @@
1
1
  // cspell: ignore logcat
2
+ // cspell: ignore usec
2
3
 
3
4
  import { AdbCommandBase, AdbSubprocessNoneProtocol } from "@yume-chan/adb";
5
+ import type { ReadableStream } from "@yume-chan/stream-extra";
4
6
  import {
5
7
  BufferedTransformStream,
6
8
  DecodeUtf8Stream,
7
9
  SplitStringStream,
8
10
  WrapReadableStream,
9
11
  WritableStream,
10
- type ReadableStream,
11
12
  } from "@yume-chan/stream-extra";
12
- import Struct, {
13
- decodeUtf8,
14
- type StructAsyncDeserializeStream,
15
- } from "@yume-chan/struct";
13
+ import type { AsyncExactReadable } from "@yume-chan/struct";
14
+ import Struct, { decodeUtf8 } from "@yume-chan/struct";
16
15
 
17
16
  // `adb logcat` is an alias to `adb shell logcat`
18
17
  // so instead of adding to core library, it's implemented here
@@ -69,10 +68,11 @@ export enum LogcatFormat {
69
68
  }
70
69
 
71
70
  export interface LogcatFormatModifiers {
72
- usec?: boolean;
71
+ microseconds?: boolean;
72
+ nanoseconds?: boolean;
73
73
  printable?: boolean;
74
74
  year?: boolean;
75
- zone?: boolean;
75
+ timezone?: boolean;
76
76
  epoch?: boolean;
77
77
  monotonic?: boolean;
78
78
  uid?: boolean;
@@ -92,44 +92,242 @@ export const LoggerEntry = new Struct({ littleEndian: true })
92
92
  .uint16("headerSize")
93
93
  .int32("pid")
94
94
  .uint32("tid")
95
- .uint32("second")
95
+ .uint32("seconds")
96
96
  .uint32("nanoseconds")
97
97
  .uint32("logId")
98
98
  .uint32("uid")
99
99
  .extra({
100
100
  get timestamp() {
101
101
  return (
102
- BigInt(this.second) * NANOSECONDS_PER_SECOND +
102
+ BigInt(this.seconds) * NANOSECONDS_PER_SECOND +
103
103
  BigInt(this.nanoseconds)
104
104
  );
105
105
  },
106
106
  });
107
107
 
108
- export type LoggerEntry = typeof LoggerEntry["TDeserializeResult"];
108
+ export type LoggerEntry = (typeof LoggerEntry)["TDeserializeResult"];
109
109
 
110
110
  // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;drc=bbe77d66e7bee8bd1f0bc7e5492b5376b0207ef6;bpv=0
111
111
  export interface AndroidLogEntry extends LoggerEntry {
112
112
  priority: AndroidLogPriority;
113
113
  tag: string;
114
114
  message: string;
115
+
116
+ toString(format?: LogcatFormat, modifiers?: LogcatFormatModifiers): string;
115
117
  }
116
118
 
117
- // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
118
- export function formatAndroidLogEntry(
119
- entry: AndroidLogEntry,
120
- format: LogcatFormat = LogcatFormat.Brief,
121
- modifier?: LogcatFormatModifiers
119
+ function padZero(number: number, length: number) {
120
+ return number.toString().padStart(length, "0");
121
+ }
122
+
123
+ function formatSeconds(seconds: number, modifiers: LogcatFormatModifiers) {
124
+ if (modifiers.monotonic) {
125
+ return padZero(seconds, 6);
126
+ }
127
+
128
+ if (modifiers.epoch) {
129
+ return padZero(seconds, 19);
130
+ }
131
+
132
+ const date = new Date(seconds * 1000);
133
+
134
+ const month = padZero(date.getMonth() + 1, 2);
135
+ const day = padZero(date.getDate(), 2);
136
+ const hour = padZero(date.getHours(), 2);
137
+ const minute = padZero(date.getMinutes(), 2);
138
+ const second = padZero(date.getSeconds(), 2);
139
+ const result = `${month}-${day} ${hour}:${minute}:${second}`;
140
+
141
+ if (modifiers.year) {
142
+ const year = padZero(date.getFullYear(), 4);
143
+ return `${year}-${result}`;
144
+ }
145
+
146
+ return result;
147
+ }
148
+
149
+ function formatNanoseconds(
150
+ nanoseconds: number,
151
+ modifiers: LogcatFormatModifiers
122
152
  ) {
123
- const uid = modifier?.uid ? `${entry.uid.toString().padStart(5)}:` : "";
153
+ if (modifiers.nanoseconds) {
154
+ return padZero(nanoseconds, 9);
155
+ }
156
+
157
+ if (modifiers.microseconds) {
158
+ return padZero(nanoseconds / 1000, 6);
159
+ }
160
+
161
+ return padZero(nanoseconds / 1000000, 3);
162
+ }
163
+
164
+ function formatTimezone(seconds: number, modifiers: LogcatFormatModifiers) {
165
+ if (!modifiers.timezone || modifiers.monotonic || modifiers.epoch) {
166
+ return "";
167
+ }
168
+
169
+ const date = new Date(seconds * 1000);
170
+ const offset = date.getTimezoneOffset();
171
+ const sign = offset <= 0 ? "+" : "-";
172
+ const absolute = Math.abs(offset);
173
+ const hours = (absolute / 60) | 0;
174
+ const minutes = absolute % 60;
175
+
176
+ // prettier-ignore
177
+ return ` ${
178
+ sign
179
+ }${
180
+ hours.toString().padStart(2, "0")
181
+ }:${
182
+ minutes.toString().padStart(2, "0")
183
+ }`;
184
+ }
185
+
186
+ function formatTime(
187
+ seconds: number,
188
+ nanoseconds: number,
189
+ modifiers: LogcatFormatModifiers
190
+ ) {
191
+ const secondsString = formatSeconds(seconds, modifiers);
192
+ const nanosecondsString = formatNanoseconds(nanoseconds, modifiers);
193
+ const zoneString = formatTimezone(seconds, modifiers);
194
+ return `${secondsString}.${nanosecondsString}${zoneString}`;
195
+ }
196
+
197
+ function formatUid(
198
+ uid: number,
199
+ modifiers: LogcatFormatModifiers,
200
+ suffix: string
201
+ ) {
202
+ return modifiers.uid ? `${uid.toString().padStart(5)}${suffix}` : "";
203
+ }
124
204
 
205
+ function getFormatPrefix(
206
+ entry: AndroidLogEntry,
207
+ format: LogcatFormat,
208
+ modifiers: LogcatFormatModifiers
209
+ ) {
210
+ // https://cs.android.com/android/platform/superproject/+/master:system/logging/liblog/logprint.cpp;l=1415;drc=8dbf3b2bb6b6d1652d9797e477b9abd03278bb79
125
211
  switch (format) {
126
212
  // TODO: implement other formats
213
+ case LogcatFormat.Tag:
214
+ // prettier-ignore
215
+ return `${
216
+ AndroidLogPriorityToCharacter[entry.priority]
217
+ }/${
218
+ entry.tag.padEnd(8)
219
+ }: `;
220
+ case LogcatFormat.Process:
221
+ // prettier-ignore
222
+ return `${
223
+ AndroidLogPriorityToCharacter[entry.priority]
224
+ }(${
225
+ formatUid(entry.uid, modifiers, ":")
226
+ }${
227
+ entry.pid.toString().padStart(5)
228
+ }) `;
229
+ case LogcatFormat.Thread:
230
+ // prettier-ignore
231
+ return `${
232
+ AndroidLogPriorityToCharacter[entry.priority]
233
+ }(${
234
+ formatUid(entry.uid, modifiers, ":")
235
+ }${
236
+ entry.pid.toString().padStart(5)
237
+ }:${
238
+ entry.tid.toString().padStart(5)
239
+ }) `;
240
+ case LogcatFormat.Raw:
241
+ return "";
242
+ case LogcatFormat.Time:
243
+ // prettier-ignore
244
+ return `${
245
+ formatTime(entry.seconds, entry.nanoseconds, modifiers)
246
+ } ${
247
+ AndroidLogPriorityToCharacter[entry.priority]
248
+ }/${
249
+ entry.tag.padEnd(8)
250
+ }(${
251
+ formatUid(entry.uid, modifiers, ":")
252
+ }${
253
+ entry.pid.toString().padStart(5)
254
+ }): `;
255
+ case LogcatFormat.ThreadTime:
256
+ // prettier-ignore
257
+ return `${
258
+ formatTime(entry.seconds, entry.nanoseconds, modifiers)
259
+ } ${
260
+ formatUid(entry.uid, modifiers, " ")
261
+ }${
262
+ entry.pid.toString().padStart(5)
263
+ } ${
264
+ entry.tid.toString().padStart(5)
265
+ } ${
266
+ AndroidLogPriorityToCharacter[entry.priority]
267
+ } ${
268
+ entry.tag.toString().padEnd(8)
269
+ }: `;
270
+ case LogcatFormat.Brief:
127
271
  default:
272
+ // prettier-ignore
128
273
  return `${
129
274
  AndroidLogPriorityToCharacter[entry.priority]
130
- }/${entry.tag.padEnd(8)}(${uid}${entry.pid
131
- .toString()
132
- .padStart(5)}): ${entry.message}`;
275
+ }/${
276
+ entry.tag.padEnd(8)
277
+ }(${
278
+ formatUid(entry.uid, modifiers, ":")
279
+ }${
280
+ entry.pid.toString().padStart(5)
281
+ }): `;
282
+ }
283
+ }
284
+
285
+ function getFormatSuffix(entry: AndroidLogEntry, format: LogcatFormat) {
286
+ switch (format) {
287
+ case LogcatFormat.Process:
288
+ return ` (${entry.tag})`;
289
+ default:
290
+ return "";
291
+ }
292
+ }
293
+
294
+ function formatEntryWrapLine(
295
+ entry: AndroidLogEntry,
296
+ format: LogcatFormat,
297
+ modifiers: LogcatFormatModifiers
298
+ ) {
299
+ const prefix = getFormatPrefix(entry, format, modifiers);
300
+ const suffix = getFormatSuffix(entry, format);
301
+ return (
302
+ prefix + entry.message.replaceAll("\n", suffix + "\n" + prefix) + suffix
303
+ );
304
+ }
305
+
306
+ function AndroidLogEntryToString(
307
+ this: AndroidLogEntry,
308
+ format: LogcatFormat = LogcatFormat.ThreadTime,
309
+ modifiers: LogcatFormatModifiers = {}
310
+ ) {
311
+ switch (format) {
312
+ case LogcatFormat.Long:
313
+ // prettier-ignore
314
+ return `[ ${
315
+ formatTime(this.seconds, this.nanoseconds, modifiers)
316
+ } ${
317
+ formatUid(this.uid, modifiers, ":")
318
+ }${
319
+ this.pid.toString().padStart(5)
320
+ }:${
321
+ this.tid.toString().padStart(5)
322
+ } ${
323
+ AndroidLogPriorityToCharacter[this.priority]
324
+ }/${
325
+ this.tag.padEnd(8)
326
+ } ]\n${
327
+ this.message
328
+ }\n`;
329
+ default:
330
+ return formatEntryWrapLine(this, format, modifiers);
133
331
  }
134
332
  }
135
333
 
@@ -150,15 +348,14 @@ function findTagEnd(payload: Uint8Array) {
150
348
  }
151
349
 
152
350
  export async function deserializeAndroidLogEntry(
153
- stream: StructAsyncDeserializeStream
351
+ stream: AsyncExactReadable
154
352
  ): Promise<AndroidLogEntry> {
155
- const entry = (await LoggerEntry.deserialize(
156
- stream
157
- )) as unknown as AndroidLogEntry;
353
+ const entry = (await LoggerEntry.deserialize(stream)) as AndroidLogEntry;
158
354
  if (entry.headerSize !== LoggerEntry.size) {
159
- await stream.read(entry.headerSize - LoggerEntry.size);
355
+ await stream.readExactly(entry.headerSize - LoggerEntry.size);
160
356
  }
161
- let payload = await stream.read(entry.payloadSize);
357
+
358
+ let payload = await stream.readExactly(entry.payloadSize);
162
359
 
163
360
  // https://cs.android.com/android/platform/superproject/+/master:system/logging/logcat/logcat.cpp;l=193-194;drc=bbe77d66e7bee8bd1f0bc7e5492b5376b0207ef6
164
361
  // TODO: payload for some log IDs are in binary format.
@@ -171,6 +368,7 @@ export async function deserializeAndroidLogEntry(
171
368
  tagEnd < payload.length - 1
172
369
  ? decodeUtf8(payload.subarray(tagEnd + 1))
173
370
  : "";
371
+ entry.toString = AndroidLogEntryToString;
174
372
  return entry;
175
373
  }
176
374
 
@@ -245,6 +443,7 @@ export class Logcat extends AdbCommandBase {
245
443
  maxEntrySize: parseInt(match[8]!, 10),
246
444
  maxPayloadSize: parseInt(match[9]!, 10),
247
445
  });
446
+ return;
248
447
  }
249
448
 
250
449
  match = chunk.match(Logcat.LOG_SIZE_REGEX_10);
@@ -0,0 +1,115 @@
1
+ import type { Adb } from "@yume-chan/adb";
2
+ import { AdbCommandBase } from "@yume-chan/adb";
3
+
4
+ import { Settings } from "./settings.js";
5
+
6
+ export interface OverlayDisplayDeviceMode {
7
+ width: number;
8
+ height: number;
9
+ density: number;
10
+ }
11
+
12
+ export interface OverlayDisplayDevice {
13
+ modes: OverlayDisplayDeviceMode[];
14
+ secure: boolean;
15
+ ownContentOnly: boolean;
16
+ showSystemDecorations: boolean;
17
+ }
18
+
19
+ export class OverlayDisplay extends AdbCommandBase {
20
+ private settings: Settings;
21
+
22
+ public static readonly OVERLAY_DISPLAY_DEVICES_KEY =
23
+ "overlay_display_devices";
24
+
25
+ constructor(adb: Adb) {
26
+ super(adb);
27
+ this.settings = new Settings(adb);
28
+ }
29
+
30
+ public async get() {
31
+ const devices: OverlayDisplayDevice[] = [];
32
+
33
+ const settingString = await this.settings.get(
34
+ "global",
35
+ OverlayDisplay.OVERLAY_DISPLAY_DEVICES_KEY
36
+ );
37
+
38
+ for (const displayString of settingString.split(";")) {
39
+ const [modesString, ...flagStrings] = displayString.split(",");
40
+
41
+ if (!modesString) {
42
+ continue;
43
+ }
44
+
45
+ const device: OverlayDisplayDevice = {
46
+ modes: [],
47
+ secure: false,
48
+ ownContentOnly: false,
49
+ showSystemDecorations: false,
50
+ };
51
+ for (const modeString of modesString.split("|")) {
52
+ const match = modeString.match(/(\d+)x(\d+)\/(\d+)/);
53
+ if (!match) {
54
+ continue;
55
+ }
56
+ device.modes.push({
57
+ width: parseInt(match[1]!, 10),
58
+ height: parseInt(match[2]!, 10),
59
+ density: parseInt(match[3]!, 10),
60
+ });
61
+ }
62
+ if (device.modes.length === 0) {
63
+ continue;
64
+ }
65
+
66
+ for (const flagString of flagStrings) {
67
+ switch (flagString) {
68
+ case "secure":
69
+ device.secure = true;
70
+ break;
71
+ case "own_content_only":
72
+ device.ownContentOnly = true;
73
+ break;
74
+ case "show_system_decorations":
75
+ device.showSystemDecorations = true;
76
+ break;
77
+ }
78
+ }
79
+ devices.push(device);
80
+ }
81
+
82
+ return devices;
83
+ }
84
+
85
+ public async set(devices: OverlayDisplayDevice[]) {
86
+ let settingString = "";
87
+ for (const device of devices) {
88
+ if (settingString) {
89
+ settingString += ";";
90
+ }
91
+
92
+ settingString += device.modes
93
+ .map((mode) => `${mode.width}x${mode.height}/${mode.density}`)
94
+ .join("|");
95
+
96
+ if (device.secure) {
97
+ settingString += ",secure";
98
+ }
99
+
100
+ if (device.ownContentOnly) {
101
+ settingString += ",own_content_only";
102
+ }
103
+
104
+ if (device.showSystemDecorations) {
105
+ settingString += ",show_system_decorations";
106
+ }
107
+ }
108
+
109
+ await this.settings.put(
110
+ "global",
111
+ OverlayDisplay.OVERLAY_DISPLAY_DEVICES_KEY,
112
+ settingString
113
+ );
114
+ }
115
+ }