mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 09:49:24 +02:00
feat(adb): update feature list (#797)
This commit is contained in:
parent
c5f282285a
commit
29de3e4842
21 changed files with 218 additions and 174 deletions
5
.changeset/four-ants-post.md
Normal file
5
.changeset/four-ants-post.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@yume-chan/adb": major
|
||||
---
|
||||
|
||||
Sync ADB feature list with latest ADB source code. Some features have been renamed to align with ADB source code. These feature flags are considered implementation details and generally not needed for outside consumers, but it's a breaking change anyway.
|
|
@ -15,8 +15,14 @@ export class AdbNoneProtocolSubprocessService {
|
|||
}
|
||||
|
||||
spawn = adbNoneProtocolSpawner(async (command, signal) => {
|
||||
// `shell,raw:${command}` also triggers raw mode,
|
||||
// But is not supported on Android version <7.
|
||||
// Android 7 added `shell,raw:${command}` service which also triggers raw mode,
|
||||
// but we want to use the most compatible one.
|
||||
//
|
||||
// Similar to SSH, we don't escape the `command`,
|
||||
// because the command will be invoked by `sh -c`,
|
||||
// it can contain environment variables (`KEY=value command`),
|
||||
// and shell expansions (`echo "$KEY"` vs `echo '$KEY'`),
|
||||
// which we can't know how to properly escape.
|
||||
const socket = await this.#adb.createSocket(
|
||||
`exec:${command.join(" ")}`,
|
||||
);
|
||||
|
@ -33,8 +39,10 @@ export class AdbNoneProtocolSubprocessService {
|
|||
command?: string | readonly string[],
|
||||
): Promise<AdbNoneProtocolPtyProcess> {
|
||||
if (command === undefined) {
|
||||
// Run the default shell
|
||||
command = "";
|
||||
} else if (Array.isArray(command)) {
|
||||
// Don't escape `command`. See `spawn` above for details
|
||||
command = command.join(" ");
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export class AdbSubprocessService {
|
|||
|
||||
this.#noneProtocol = new AdbNoneProtocolSubprocessService(adb);
|
||||
|
||||
if (adb.canUseFeature(AdbFeature.ShellV2)) {
|
||||
if (adb.canUseFeature(AdbFeature.Shell2)) {
|
||||
this.#shellProtocol = new AdbShellProtocolSubprocessService(adb);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export class AdbShellProtocolSubprocessService {
|
|||
}
|
||||
|
||||
get isSupported() {
|
||||
return this.#adb.canUseFeature(AdbFeature.ShellV2);
|
||||
return this.#adb.canUseFeature(AdbFeature.Shell2);
|
||||
}
|
||||
|
||||
constructor(adb: Adb) {
|
||||
|
@ -20,6 +20,7 @@ export class AdbShellProtocolSubprocessService {
|
|||
}
|
||||
|
||||
spawn = adbShellProtocolSpawner(async (command, signal) => {
|
||||
// Don't escape `command`. See `AdbNoneProtocolSubprocessService.prototype.spawn` for details.
|
||||
const socket = await this.#adb.createSocket(
|
||||
`shell,v2,raw:${command.join(" ")}`,
|
||||
);
|
||||
|
@ -45,6 +46,7 @@ export class AdbShellProtocolSubprocessService {
|
|||
service += ":";
|
||||
|
||||
if (options) {
|
||||
// Don't escape `command`. See `AdbNoneProtocolSubprocessService.prototype.spawn` for details.
|
||||
if (typeof options.command === "string") {
|
||||
service += options.command;
|
||||
} else if (Array.isArray(options.command)) {
|
||||
|
|
33
libraries/adb/src/commands/sync/id-common.ts
Normal file
33
libraries/adb/src/commands/sync/id-common.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
||||
|
||||
function encodeAsciiUnchecked(value: string): Uint8Array<ArrayBuffer> {
|
||||
const result = new Uint8Array(value.length);
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
result[i] = value.charCodeAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode ID to numbers for faster comparison.
|
||||
*
|
||||
* This function skips all checks. The caller must ensure the input is valid.
|
||||
*
|
||||
* @param value A 4 ASCII character string.
|
||||
* @returns A 32-bit integer by encoding the string as little-endian
|
||||
*
|
||||
* #__NO_SIDE_EFFECTS__
|
||||
*/
|
||||
export function adbSyncEncodeId(value: string): number {
|
||||
const buffer = encodeAsciiUnchecked(value);
|
||||
return getUint32LittleEndian(buffer, 0);
|
||||
}
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/file_sync_protocol.h;l=23;drc=888a54dcbf954fdffacc8283a793290abcc589cd
|
||||
|
||||
export const Lstat = adbSyncEncodeId("STAT");
|
||||
export const Stat = adbSyncEncodeId("STA2");
|
||||
export const LstatV2 = adbSyncEncodeId("LST2");
|
||||
|
||||
export const Done = adbSyncEncodeId("DONE");
|
||||
export const Data = adbSyncEncodeId("DATA");
|
12
libraries/adb/src/commands/sync/id-request.ts
Normal file
12
libraries/adb/src/commands/sync/id-request.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { adbSyncEncodeId } from "./id-common.js";
|
||||
|
||||
export { Data, Done, Lstat, LstatV2, Stat } from "./id-common.js";
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/file_sync_protocol.h;l=23;drc=888a54dcbf954fdffacc8283a793290abcc589cd
|
||||
|
||||
export const List = adbSyncEncodeId("LIST");
|
||||
export const ListV2 = adbSyncEncodeId("LIS2");
|
||||
|
||||
export const Send = adbSyncEncodeId("SEND");
|
||||
export const SendV2 = adbSyncEncodeId("SND2");
|
||||
export const Receive = adbSyncEncodeId("RECV");
|
11
libraries/adb/src/commands/sync/id-response.ts
Normal file
11
libraries/adb/src/commands/sync/id-response.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { adbSyncEncodeId } from "./id-common.js";
|
||||
|
||||
export { Data, Done, Lstat, LstatV2, Stat } from "./id-common.js";
|
||||
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/file_sync_protocol.h;l=23;drc=888a54dcbf954fdffacc8283a793290abcc589cd
|
||||
|
||||
export const Entry = adbSyncEncodeId("DENT");
|
||||
export const EntryV2 = adbSyncEncodeId("DNT2");
|
||||
|
||||
export const Ok = adbSyncEncodeId("OKAY");
|
||||
export const Fail = adbSyncEncodeId("FAIL");
|
7
libraries/adb/src/commands/sync/id.ts
Normal file
7
libraries/adb/src/commands/sync/id.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import * as AdbSyncRequestId from "./id-request.js";
|
||||
import * as AdbSyncResponseId from "./id-response.js";
|
||||
|
||||
// Values of `AdbSyncRequestId` and `AdbSyncResponseId` are all generic `number`s
|
||||
// so there is no point creating types for them
|
||||
|
||||
export { AdbSyncRequestId, AdbSyncResponseId };
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./id.js";
|
||||
export * from "./list.js";
|
||||
export * from "./pull.js";
|
||||
export * from "./push.js";
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import type { StructValue } from "@yume-chan/struct";
|
||||
import { extend, string, u32 } from "@yume-chan/struct";
|
||||
|
||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
||||
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
|
||||
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||
import { adbSyncWriteRequest } from "./request.js";
|
||||
import { adbSyncReadResponses } from "./response.js";
|
||||
import type { AdbSyncSocket } from "./socket.js";
|
||||
import type { AdbSyncStat } from "./stat.js";
|
||||
import {
|
||||
|
@ -36,7 +37,7 @@ export async function* adbSyncOpenDirV2(
|
|||
await adbSyncWriteRequest(locked, AdbSyncRequestId.ListV2, path);
|
||||
for await (const item of adbSyncReadResponses(
|
||||
locked,
|
||||
AdbSyncResponseId.Entry2,
|
||||
AdbSyncResponseId.EntryV2,
|
||||
AdbSyncEntry2Response,
|
||||
)) {
|
||||
// `LST2` can return error codes for failed `lstat` calls.
|
||||
|
|
|
@ -2,8 +2,9 @@ import { ReadableStream } from "@yume-chan/stream-extra";
|
|||
import type { StructValue } from "@yume-chan/struct";
|
||||
import { buffer, struct, u32 } from "@yume-chan/struct";
|
||||
|
||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
||||
import { adbSyncReadResponses, AdbSyncResponseId } from "./response.js";
|
||||
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||
import { adbSyncWriteRequest } from "./request.js";
|
||||
import { adbSyncReadResponses } from "./response.js";
|
||||
import type { AdbSyncSocket } from "./socket.js";
|
||||
|
||||
export const AdbSyncDataResponse = struct(
|
||||
|
|
|
@ -8,8 +8,9 @@ import { struct, u32 } from "@yume-chan/struct";
|
|||
|
||||
import { NOOP } from "../../utils/index.js";
|
||||
|
||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
||||
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
|
||||
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||
import { adbSyncWriteRequest } from "./request.js";
|
||||
import { adbSyncReadResponse } from "./response.js";
|
||||
import type { AdbSyncSocket, AdbSyncSocketLocked } from "./socket.js";
|
||||
import { LinuxFileType } from "./stat.js";
|
||||
|
||||
|
|
|
@ -1,20 +1,5 @@
|
|||
import { encodeUtf8, struct, u32 } from "@yume-chan/struct";
|
||||
|
||||
import { adbSyncEncodeId } from "./response.js";
|
||||
|
||||
export const AdbSyncRequestId = {
|
||||
List: adbSyncEncodeId("LIST"),
|
||||
ListV2: adbSyncEncodeId("LIS2"),
|
||||
Send: adbSyncEncodeId("SEND"),
|
||||
SendV2: adbSyncEncodeId("SND2"),
|
||||
Lstat: adbSyncEncodeId("STAT"),
|
||||
Stat: adbSyncEncodeId("STA2"),
|
||||
LstatV2: adbSyncEncodeId("LST2"),
|
||||
Data: adbSyncEncodeId("DATA"),
|
||||
Done: adbSyncEncodeId("DONE"),
|
||||
Receive: adbSyncEncodeId("RECV"),
|
||||
} as const;
|
||||
|
||||
export const AdbSyncNumberRequest = struct(
|
||||
{ id: u32, arg: u32 },
|
||||
{ littleEndian: true },
|
||||
|
@ -26,13 +11,9 @@ export interface AdbSyncWritable {
|
|||
|
||||
export async function adbSyncWriteRequest(
|
||||
writable: AdbSyncWritable,
|
||||
id: number | string,
|
||||
id: number,
|
||||
value: number | string | Uint8Array,
|
||||
): Promise<void> {
|
||||
if (typeof id === "string") {
|
||||
id = adbSyncEncodeId(id);
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
await writable.write(
|
||||
AdbSyncNumberRequest.serialize({ id, arg: value }),
|
||||
|
|
|
@ -2,39 +2,9 @@ import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
|||
import type { AsyncExactReadable, StructDeserializer } from "@yume-chan/struct";
|
||||
import { decodeUtf8, string, struct, u32 } from "@yume-chan/struct";
|
||||
|
||||
import { unreachable } from "../../utils/no-op.js";
|
||||
import { unreachable } from "../../utils/index.js";
|
||||
|
||||
function encodeAsciiUnchecked(value: string): Uint8Array<ArrayBuffer> {
|
||||
const result = new Uint8Array(value.length);
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
result[i] = value.charCodeAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode ID to numbers for faster comparison
|
||||
* @param value A 4-character string
|
||||
* @returns A 32-bit integer by encoding the string as little-endian
|
||||
*
|
||||
* #__NO_SIDE_EFFECTS__
|
||||
*/
|
||||
export function adbSyncEncodeId(value: string): number {
|
||||
const buffer = encodeAsciiUnchecked(value);
|
||||
return getUint32LittleEndian(buffer, 0);
|
||||
}
|
||||
|
||||
export const AdbSyncResponseId = {
|
||||
Entry: adbSyncEncodeId("DENT"),
|
||||
Entry2: adbSyncEncodeId("DNT2"),
|
||||
Lstat: adbSyncEncodeId("STAT"),
|
||||
Stat: adbSyncEncodeId("STA2"),
|
||||
Lstat2: adbSyncEncodeId("LST2"),
|
||||
Done: adbSyncEncodeId("DONE"),
|
||||
Data: adbSyncEncodeId("DATA"),
|
||||
Ok: adbSyncEncodeId("OKAY"),
|
||||
Fail: adbSyncEncodeId("FAIL"),
|
||||
};
|
||||
import { AdbSyncResponseId } from "./id.js";
|
||||
|
||||
export class AdbSyncError extends Error {}
|
||||
|
||||
|
@ -50,18 +20,14 @@ export const AdbSyncFailResponse = struct(
|
|||
|
||||
export async function adbSyncReadResponse<T>(
|
||||
stream: AsyncExactReadable,
|
||||
id: number | string,
|
||||
id: number,
|
||||
type: StructDeserializer<T>,
|
||||
): Promise<T> {
|
||||
if (typeof id === "string") {
|
||||
id = adbSyncEncodeId(id);
|
||||
}
|
||||
|
||||
const buffer = await stream.readExactly(4);
|
||||
switch (getUint32LittleEndian(buffer, 0)) {
|
||||
case AdbSyncResponseId.Fail:
|
||||
await AdbSyncFailResponse.deserialize(stream);
|
||||
throw new Error("Unreachable");
|
||||
unreachable();
|
||||
case id:
|
||||
return await type.deserialize(stream);
|
||||
default:
|
||||
|
@ -73,13 +39,9 @@ export async function adbSyncReadResponse<T>(
|
|||
|
||||
export async function* adbSyncReadResponses<T>(
|
||||
stream: AsyncExactReadable,
|
||||
id: number | string,
|
||||
id: number,
|
||||
type: StructDeserializer<T>,
|
||||
): AsyncGenerator<T, void, void> {
|
||||
if (typeof id === "string") {
|
||||
id = adbSyncEncodeId(id);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const buffer = await stream.readExactly(4);
|
||||
switch (getUint32LittleEndian(buffer, 0)) {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import type { StructValue } from "@yume-chan/struct";
|
||||
import { struct, u32, u64 } from "@yume-chan/struct";
|
||||
|
||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
||||
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
|
||||
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||
import { adbSyncWriteRequest } from "./request.js";
|
||||
import { adbSyncReadResponse } from "./response.js";
|
||||
import type { AdbSyncSocket } from "./socket.js";
|
||||
|
||||
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
|
||||
|
@ -131,7 +132,7 @@ export async function adbSyncLstat(
|
|||
await adbSyncWriteRequest(locked, AdbSyncRequestId.LstatV2, path);
|
||||
return await adbSyncReadResponse(
|
||||
locked,
|
||||
AdbSyncResponseId.Lstat2,
|
||||
AdbSyncResponseId.LstatV2,
|
||||
AdbSyncStatResponse,
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -21,7 +21,7 @@ import { adbSyncLstat, adbSyncStat } from "./stat.js";
|
|||
export function dirname(path: string): string {
|
||||
const end = path.lastIndexOf("/");
|
||||
if (end === -1) {
|
||||
throw new Error(`Invalid path`);
|
||||
throw new Error(`Invalid absolute unix path: ${path}`);
|
||||
}
|
||||
if (end === 0) {
|
||||
return "/";
|
||||
|
@ -43,25 +43,25 @@ export class AdbSync {
|
|||
protected _socket: AdbSyncSocket;
|
||||
|
||||
readonly #supportsStat: boolean;
|
||||
readonly #supportsListV2: boolean;
|
||||
readonly #supportsLs2: boolean;
|
||||
readonly #fixedPushMkdir: boolean;
|
||||
readonly #supportsSendReceiveV2: boolean;
|
||||
readonly #supportsSendReceive2: boolean;
|
||||
readonly #needPushMkdirWorkaround: boolean;
|
||||
|
||||
get supportsStat(): boolean {
|
||||
return this.#supportsStat;
|
||||
}
|
||||
|
||||
get supportsListV2(): boolean {
|
||||
return this.#supportsListV2;
|
||||
get supportsLs2(): boolean {
|
||||
return this.#supportsLs2;
|
||||
}
|
||||
|
||||
get fixedPushMkdir(): boolean {
|
||||
return this.#fixedPushMkdir;
|
||||
}
|
||||
|
||||
get supportsSendReceiveV2(): boolean {
|
||||
return this.#supportsSendReceiveV2;
|
||||
get supportsSendReceive2(): boolean {
|
||||
return this.#supportsSendReceive2;
|
||||
}
|
||||
|
||||
get needPushMkdirWorkaround(): boolean {
|
||||
|
@ -72,15 +72,13 @@ export class AdbSync {
|
|||
this._adb = adb;
|
||||
this._socket = new AdbSyncSocket(socket, adb.maxPayloadSize);
|
||||
|
||||
this.#supportsStat = adb.canUseFeature(AdbFeature.StatV2);
|
||||
this.#supportsListV2 = adb.canUseFeature(AdbFeature.ListV2);
|
||||
this.#supportsStat = adb.canUseFeature(AdbFeature.Stat2);
|
||||
this.#supportsLs2 = adb.canUseFeature(AdbFeature.Ls2);
|
||||
this.#fixedPushMkdir = adb.canUseFeature(AdbFeature.FixedPushMkdir);
|
||||
this.#supportsSendReceiveV2 = adb.canUseFeature(
|
||||
AdbFeature.SendReceiveV2,
|
||||
);
|
||||
this.#supportsSendReceive2 = adb.canUseFeature(AdbFeature.SendReceive2);
|
||||
// https://android.googlesource.com/platform/packages/modules/adb/+/91768a57b7138166e0a3d11f79cd55909dda7014/client/file_sync_client.cpp#1361
|
||||
this.#needPushMkdirWorkaround =
|
||||
this._adb.canUseFeature(AdbFeature.ShellV2) && !this.fixedPushMkdir;
|
||||
this._adb.canUseFeature(AdbFeature.Shell2) && !this.fixedPushMkdir;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,7 +118,7 @@ export class AdbSync {
|
|||
}
|
||||
|
||||
opendir(path: string): AsyncGenerator<AdbSyncEntry, void, void> {
|
||||
return adbSyncOpenDir(this._socket, path, this.supportsListV2);
|
||||
return adbSyncOpenDir(this._socket, path, this.supportsLs2);
|
||||
}
|
||||
|
||||
async readdir(path: string) {
|
||||
|
@ -157,7 +155,7 @@ export class AdbSync {
|
|||
}
|
||||
|
||||
await adbSyncPush({
|
||||
v2: this.supportsSendReceiveV2,
|
||||
v2: this.supportsSendReceive2,
|
||||
socket: this._socket,
|
||||
...options,
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ import type {
|
|||
AdbTransport,
|
||||
} from "../adb.js";
|
||||
import { AdbBanner } from "../banner.js";
|
||||
import { AdbFeature } from "../features.js";
|
||||
import { AdbDeviceFeatures, AdbFeature } from "../features.js";
|
||||
|
||||
import type { AdbAuthenticator, AdbCredentialStore } from "./auth.js";
|
||||
import { AdbDefaultAuthenticator } from "./auth.js";
|
||||
|
@ -23,30 +23,6 @@ import type { AdbPacketData, AdbPacketInit } from "./packet.js";
|
|||
import { AdbCommand, calculateChecksum } from "./packet.js";
|
||||
|
||||
export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001;
|
||||
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
||||
// There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
|
||||
export const ADB_DAEMON_DEFAULT_FEATURES = /* #__PURE__ */ (() =>
|
||||
[
|
||||
AdbFeature.ShellV2,
|
||||
AdbFeature.Cmd,
|
||||
AdbFeature.StatV2,
|
||||
AdbFeature.ListV2,
|
||||
AdbFeature.FixedPushMkdir,
|
||||
"apex",
|
||||
AdbFeature.Abb,
|
||||
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
||||
// No special handling required.
|
||||
"fixed_push_symlink_timestamp",
|
||||
AdbFeature.AbbExec,
|
||||
"remount_shell",
|
||||
"track_app",
|
||||
AdbFeature.SendReceiveV2,
|
||||
"sendrecv_v2_brotli",
|
||||
"sendrecv_v2_lz4",
|
||||
"sendrecv_v2_zstd",
|
||||
"sendrecv_v2_dry_run_send",
|
||||
AdbFeature.DelayedAck,
|
||||
] as readonly AdbFeature[])();
|
||||
export const ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE = 32 * 1024 * 1024;
|
||||
|
||||
export type AdbDaemonConnection = ReadableWritablePair<
|
||||
|
@ -154,7 +130,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
static async authenticate({
|
||||
serial,
|
||||
connection,
|
||||
features = ADB_DAEMON_DEFAULT_FEATURES,
|
||||
features = AdbDeviceFeatures,
|
||||
initialDelayedAckBytes = ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE,
|
||||
...options
|
||||
}: AdbDaemonAuthenticationOptions &
|
||||
|
@ -251,11 +227,10 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
);
|
||||
}
|
||||
|
||||
const actualFeatures = features.slice();
|
||||
if (initialDelayedAckBytes <= 0) {
|
||||
const index = features.indexOf(AdbFeature.DelayedAck);
|
||||
if (index !== -1) {
|
||||
actualFeatures.splice(index, 1);
|
||||
features = features.toSpliced(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,9 +242,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
arg1: maxPayloadSize,
|
||||
// The terminating `;` is required in formal definition
|
||||
// But ADB daemon (all versions) can still work without it
|
||||
payload: encodeUtf8(
|
||||
`host::features=${actualFeatures.join(",")}`,
|
||||
),
|
||||
payload: encodeUtf8(`host::features=${features.join(",")}`),
|
||||
});
|
||||
|
||||
banner = await resolver.promise;
|
||||
|
@ -289,7 +262,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
version,
|
||||
maxPayloadSize,
|
||||
banner,
|
||||
features: actualFeatures,
|
||||
features,
|
||||
initialDelayedAckBytes,
|
||||
preserveConnection: options.preserveConnection,
|
||||
readTimeLimit: options.readTimeLimit,
|
||||
|
@ -336,7 +309,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
connection,
|
||||
version,
|
||||
banner,
|
||||
features = ADB_DAEMON_DEFAULT_FEATURES,
|
||||
features = AdbDeviceFeatures,
|
||||
initialDelayedAckBytes,
|
||||
...options
|
||||
}: AdbDaemonSocketConnectorConstructionOptions) {
|
||||
|
@ -359,19 +332,19 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
initialDelayedAckBytes = 0;
|
||||
}
|
||||
|
||||
let calculateChecksum: boolean;
|
||||
let appendNullToServiceString: boolean;
|
||||
let shouldCalculateChecksum: boolean;
|
||||
let shouldAppendNullToServiceString: boolean;
|
||||
if (version >= ADB_DAEMON_VERSION_OMIT_CHECKSUM) {
|
||||
calculateChecksum = false;
|
||||
appendNullToServiceString = false;
|
||||
shouldCalculateChecksum = false;
|
||||
shouldAppendNullToServiceString = false;
|
||||
} else {
|
||||
calculateChecksum = true;
|
||||
appendNullToServiceString = true;
|
||||
shouldCalculateChecksum = true;
|
||||
shouldAppendNullToServiceString = true;
|
||||
}
|
||||
|
||||
this.#dispatcher = new AdbPacketDispatcher(connection, {
|
||||
calculateChecksum,
|
||||
appendNullToServiceString,
|
||||
calculateChecksum: shouldCalculateChecksum,
|
||||
appendNullToServiceString: shouldAppendNullToServiceString,
|
||||
initialDelayedAckBytes,
|
||||
...options,
|
||||
});
|
||||
|
|
47
libraries/adb/src/features-value.ts
Normal file
47
libraries/adb/src/features-value.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
// cspell: ignore Libusb
|
||||
// cspell: ignore devraw
|
||||
// cspell: ignore Openscreen
|
||||
// cspell: ignore devicetracker
|
||||
|
||||
// The order follows
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/transport.cpp;l=81;drc=2d3e62c2af54a3e8f8803ea10492e63b8dfe709f
|
||||
|
||||
export const Shell2 = "shell_v2";
|
||||
export const Cmd = "cmd";
|
||||
export const Stat2 = "stat_v2";
|
||||
export const Ls2 = "ls_v2";
|
||||
/**
|
||||
* server only
|
||||
*/
|
||||
export const Libusb = "libusb";
|
||||
/**
|
||||
* server only
|
||||
*/
|
||||
export const PushSync = "push_sync";
|
||||
export const Apex = "apex";
|
||||
export const FixedPushMkdir = "fixed_push_mkdir";
|
||||
export const Abb = "abb";
|
||||
export const FixedPushSymlinkTimestamp = "fixed_push_symlink_timestamp";
|
||||
export const AbbExec = "abb_exec";
|
||||
export const RemountShell = "remount_shell";
|
||||
export const TrackApp = "track_app";
|
||||
export const SendReceive2 = "sendrecv_v2";
|
||||
export const SendReceive2Brotli = "sendrecv_v2_brotli";
|
||||
export const SendReceive2Lz4 = "sendrecv_v2_lz4";
|
||||
export const SendReceive2Zstd = "sendrecv_v2_zstd";
|
||||
export const SendReceive2DryRunSend = "sendrecv_v2_dry_run_send";
|
||||
export const DelayedAck = "delayed_ack";
|
||||
/**
|
||||
* server only
|
||||
*/
|
||||
export const OpenscreenMdns = "openscreen_mdns";
|
||||
/**
|
||||
* server only
|
||||
*/
|
||||
export const DeviceTrackerProtoFormat = "devicetracker_proto_format";
|
||||
export const DevRaw = "devraw";
|
||||
export const AppInfo = "app_info";
|
||||
/**
|
||||
* server only
|
||||
*/
|
||||
export const ServerStatus = "server_status";
|
|
@ -1,15 +1,32 @@
|
|||
// The order follows
|
||||
// https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/transport.cpp;l=77;drc=6d14d35d0241f6fee145f8e54ffd77252e8d29fd
|
||||
export const AdbFeature = {
|
||||
ShellV2: "shell_v2",
|
||||
Cmd: "cmd",
|
||||
StatV2: "stat_v2",
|
||||
ListV2: "ls_v2",
|
||||
FixedPushMkdir: "fixed_push_mkdir",
|
||||
Abb: "abb",
|
||||
AbbExec: "abb_exec",
|
||||
SendReceiveV2: "sendrecv_v2",
|
||||
DelayedAck: "delayed_ack",
|
||||
} as const;
|
||||
import * as AdbFeature from "./features-value.js";
|
||||
|
||||
export type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature];
|
||||
// biome-ignore lint/suspicious/noRedeclare: TypeScript declaration merging for enum-like object
|
||||
type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature];
|
||||
|
||||
export { AdbFeature };
|
||||
|
||||
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252
|
||||
// There are some other feature constants, but some of them are only used by ADB server, not devices (daemons).
|
||||
export const AdbDeviceFeatures = [
|
||||
AdbFeature.Shell2,
|
||||
AdbFeature.Cmd,
|
||||
AdbFeature.Stat2,
|
||||
AdbFeature.Ls2,
|
||||
AdbFeature.FixedPushMkdir,
|
||||
AdbFeature.Apex,
|
||||
AdbFeature.Abb,
|
||||
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
||||
// No special handling required.
|
||||
AdbFeature.FixedPushSymlinkTimestamp,
|
||||
AdbFeature.AbbExec,
|
||||
AdbFeature.RemountShell,
|
||||
AdbFeature.TrackApp,
|
||||
AdbFeature.SendReceive2,
|
||||
AdbFeature.SendReceive2Brotli,
|
||||
AdbFeature.SendReceive2Lz4,
|
||||
AdbFeature.SendReceive2Zstd,
|
||||
AdbFeature.SendReceive2DryRunSend,
|
||||
AdbFeature.DevRaw,
|
||||
AdbFeature.AppInfo,
|
||||
AdbFeature.DelayedAck,
|
||||
] as readonly AdbFeature[];
|
||||
|
|
|
@ -6,32 +6,10 @@ import type {
|
|||
AdbTransport,
|
||||
} from "../adb.js";
|
||||
import type { AdbBanner } from "../banner.js";
|
||||
import { AdbFeature } from "../features.js";
|
||||
import { AdbDeviceFeatures } from "../features.js";
|
||||
|
||||
import type { AdbServerClient } from "./client.js";
|
||||
|
||||
export const ADB_SERVER_DEFAULT_FEATURES = /* #__PURE__ */ (() =>
|
||||
[
|
||||
AdbFeature.ShellV2,
|
||||
AdbFeature.Cmd,
|
||||
AdbFeature.StatV2,
|
||||
AdbFeature.ListV2,
|
||||
AdbFeature.FixedPushMkdir,
|
||||
"apex",
|
||||
AdbFeature.Abb,
|
||||
// only tells the client the symlink timestamp issue in `adb push --sync` has been fixed.
|
||||
// No special handling required.
|
||||
"fixed_push_symlink_timestamp",
|
||||
AdbFeature.AbbExec,
|
||||
"remount_shell",
|
||||
"track_app",
|
||||
AdbFeature.SendReceiveV2,
|
||||
"sendrecv_v2_brotli",
|
||||
"sendrecv_v2_lz4",
|
||||
"sendrecv_v2_zstd",
|
||||
"sendrecv_v2_dry_run_send",
|
||||
] as readonly AdbFeature[])();
|
||||
|
||||
export class AdbServerTransport implements AdbTransport {
|
||||
#client: AdbServerClient;
|
||||
|
||||
|
@ -52,9 +30,14 @@ export class AdbServerTransport implements AdbTransport {
|
|||
}
|
||||
|
||||
get clientFeatures() {
|
||||
// No need to get host features (features supported by ADB server)
|
||||
// Because we create all ADB packets ourselves
|
||||
return ADB_SERVER_DEFAULT_FEATURES;
|
||||
// This list tells the `Adb` instance how to invoke some commands.
|
||||
//
|
||||
// Because all device commands are created by the `Adb` instance, not ADB server,
|
||||
// we don't need to fetch current server's feature list using `host-features` command.
|
||||
//
|
||||
// And because all server commands are created by the `AdbServerClient` instance, not `Adb`,
|
||||
// we don't need to pass server-only features to `Adb` in this list.
|
||||
return AdbDeviceFeatures;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/max-params
|
||||
|
|
|
@ -43,7 +43,7 @@ export class ActivityManager extends AdbServiceBase {
|
|||
options: ActivityManagerStartActivityOptions,
|
||||
): Promise<void> {
|
||||
// Android 8 added "start-activity" alias to "start"
|
||||
// Use the most compatible command
|
||||
// but we want to use the most compatible one.
|
||||
const command = buildCommand(
|
||||
[ActivityManager.ServiceName, "start", "-W"],
|
||||
options,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue