Skip to content

Commit d47fb3b

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
feat: codec2 E2E tests, enhanced examples, parcel read/write helpers
Add Codec2 encoder E2E tests (ListComponents, CreateEncoder, EncodeFrame) and a standalone codec2_encode example. Rewrite clipboard_monitor to do full copy-paste round-trips using a manual ClipData plain-text marshaler (clipdata_plaintext.go). Enhance input_injector with structured CLI and sensor_gateway with improved error handling. Add parcel helpers: WritePlainCharSequence/ReadPlainCharSequence for TextUtils CharSequence wire format, WriteStringList/ReadStringList for Java Parcel string lists, and WriteNullString for null string8 values. Fix CI: exclude native_impls packages from GO_PACKAGES in Makefile (they require CGO/NDK). Add built example binaries to .gitignore.
1 parent 8a4cab7 commit d47fb3b

10 files changed

Lines changed: 1592 additions & 91 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
/aidl2spec
4040
/getservice_vs_checkservice
4141

42+
# Example binaries built to repo root
43+
/clipboard_monitor
44+
/input_injector
45+
/sensor_gateway
46+
/sensor_reader
47+
4248
# Lean 4
4349
/.lake
4450
proofs/.lake

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
GENERATED_DIRS := android com fuzztest libgui_test_server parcelables src
77

88
# All non-3rdparty Go packages.
9-
GO_PACKAGES = $(shell go list -e ./... | grep -v /3rdparty/)
9+
GO_PACKAGES = $(shell go list -e ./... | grep -v -e /3rdparty/ -e /native_impls/)
1010

1111
# --- Android NDK / GrapheneOS paths ---
1212

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package content
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/AndroidGoLab/binder/parcel"
7+
)
8+
9+
// MIMETypePlainText is the MIME type for plain text clipboard entries.
10+
const MIMETypePlainText = "text/plain"
11+
12+
// MarshalPlainTextClipData writes a ClipData containing a single plain-text
13+
// item to the parcel. This matches ClipData.newPlainText(label, text) on the
14+
// Java side.
15+
//
16+
// Wire format (matching ClipData.writeToParcel + ClipDescription.writeToParcel):
17+
//
18+
// ClipDescription:
19+
// CharSequence label (TextUtils.writeToParcel: kind=1, string8)
20+
// StringList mimeTypes (writeStringList: count, then string16 per entry)
21+
// PersistableBundle extras (null = int32(-1))
22+
// int64 timestamp (0)
23+
// bool isStyledText (false)
24+
// int32 classStatus (0)
25+
// Bundle confidences (null = int32(-1))
26+
// int32 iconFlag (0 = no icon)
27+
// int32 N (1 item)
28+
// Item:
29+
// CharSequence text (TextUtils.writeToParcel: kind=1, string8)
30+
// string8 htmlText (null = int32(-1))
31+
// TypedObject intent (null = int32(0))
32+
// TypedObject intentSender (null = int32(0))
33+
// TypedObject uri (null = int32(0))
34+
// TypedObject activityInfo (null = int32(0))
35+
// TypedObject textLinks (null = int32(0))
36+
func MarshalPlainTextClipData(
37+
p *parcel.Parcel,
38+
label string,
39+
text string,
40+
) {
41+
// ClipDescription
42+
parcel.WritePlainCharSequence(p, &label) // mLabel
43+
p.WriteStringList([]string{MIMETypePlainText}) // mMimeTypes
44+
p.WriteInt32(-1) // mExtras (null PersistableBundle)
45+
p.WriteInt64(0) // mTimeStamp
46+
p.WriteBool(false) // mIsStyledText
47+
p.WriteInt32(0) // mClassificationStatus
48+
p.WriteInt32(-1) // confidences bundle (null)
49+
50+
// Icon
51+
p.WriteInt32(0) // no icon
52+
53+
// Items
54+
p.WriteInt32(1) // N = 1
55+
56+
// Item[0]
57+
parcel.WritePlainCharSequence(p, &text) // mText
58+
p.WriteNullString() // mHtmlText (null string8)
59+
p.WriteInt32(0) // mIntent (null typed object)
60+
p.WriteInt32(0) // mIntentSender (null typed object)
61+
p.WriteInt32(0) // mUri (null typed object)
62+
p.WriteInt32(0) // mActivityInfo (null typed object)
63+
p.WriteInt32(0) // mTextLinks (null typed object)
64+
}
65+
66+
// ClipDataText holds the text content extracted from a ClipData parcel.
67+
type ClipDataText struct {
68+
Label string
69+
MIMETypes []string
70+
Items []string
71+
}
72+
73+
// UnmarshalClipDataText reads a ClipData from the parcel and extracts plain
74+
// text content. Non-text fields (intents, URIs, etc.) are skipped.
75+
//
76+
// This reads the same wire format written by MarshalPlainTextClipData and by
77+
// Android's ClipData.writeToParcel.
78+
func UnmarshalClipDataText(
79+
p *parcel.Parcel,
80+
) (ClipDataText, error) {
81+
var result ClipDataText
82+
83+
// ClipDescription
84+
label, err := parcel.ReadPlainCharSequence(p)
85+
if err != nil {
86+
return result, fmt.Errorf("reading label: %w", err)
87+
}
88+
if label != nil {
89+
result.Label = *label
90+
}
91+
92+
mimeTypes, err := p.ReadStringList()
93+
if err != nil {
94+
return result, fmt.Errorf("reading mimeTypes: %w", err)
95+
}
96+
result.MIMETypes = mimeTypes
97+
98+
// Skip PersistableBundle (extras): null = -1, otherwise length-prefixed.
99+
if err := skipBundle(p); err != nil {
100+
return result, fmt.Errorf("skipping extras: %w", err)
101+
}
102+
103+
// timestamp, isStyledText, classificationStatus
104+
if _, err := p.ReadInt64(); err != nil {
105+
return result, fmt.Errorf("reading timestamp: %w", err)
106+
}
107+
if _, err := p.ReadBool(); err != nil {
108+
return result, fmt.Errorf("reading isStyledText: %w", err)
109+
}
110+
if _, err := p.ReadInt32(); err != nil {
111+
return result, fmt.Errorf("reading classificationStatus: %w", err)
112+
}
113+
114+
// Skip confidences bundle
115+
if err := skipBundle(p); err != nil {
116+
return result, fmt.Errorf("skipping confidences: %w", err)
117+
}
118+
119+
// Icon
120+
iconFlag, err := p.ReadInt32()
121+
if err != nil {
122+
return result, fmt.Errorf("reading icon flag: %w", err)
123+
}
124+
if iconFlag != 0 {
125+
// Bitmap follows; we cannot skip it without knowing the wire size.
126+
return result, fmt.Errorf("non-null icon (Bitmap): unsupported")
127+
}
128+
129+
// Items
130+
n, err := p.ReadInt32()
131+
if err != nil {
132+
return result, fmt.Errorf("reading item count: %w", err)
133+
}
134+
135+
for i := int32(0); i < n; i++ {
136+
text, err := parcel.ReadPlainCharSequence(p)
137+
if err != nil {
138+
return result, fmt.Errorf("item[%d] text: %w", i, err)
139+
}
140+
141+
// htmlText (string8, nullable)
142+
if _, err := p.ReadNullableString(); err != nil {
143+
return result, fmt.Errorf("item[%d] htmlText: %w", i, err)
144+
}
145+
146+
// intent (typed object)
147+
if err := skipTypedObject(p); err != nil {
148+
return result, fmt.Errorf("item[%d] intent: %w", i, err)
149+
}
150+
151+
// intentSender (typed object)
152+
if err := skipTypedObject(p); err != nil {
153+
return result, fmt.Errorf("item[%d] intentSender: %w", i, err)
154+
}
155+
156+
// uri (typed object)
157+
if err := skipTypedObject(p); err != nil {
158+
return result, fmt.Errorf("item[%d] uri: %w", i, err)
159+
}
160+
161+
// activityInfo (typed object)
162+
if err := skipTypedObject(p); err != nil {
163+
return result, fmt.Errorf("item[%d] activityInfo: %w", i, err)
164+
}
165+
166+
// textLinks (typed object)
167+
if err := skipTypedObject(p); err != nil {
168+
return result, fmt.Errorf("item[%d] textLinks: %w", i, err)
169+
}
170+
171+
if text != nil {
172+
result.Items = append(result.Items, *text)
173+
}
174+
}
175+
176+
return result, nil
177+
}
178+
179+
// skipBundle skips a Bundle or PersistableBundle in the parcel.
180+
// Null bundles are represented as int32(-1). Non-null bundles have an int32
181+
// byte length followed by that many bytes of data.
182+
func skipBundle(
183+
p *parcel.Parcel,
184+
) error {
185+
length, err := p.ReadInt32()
186+
if err != nil {
187+
return err
188+
}
189+
if length > 0 {
190+
p.SetPosition(p.Position() + int(length))
191+
}
192+
return nil
193+
}
194+
195+
// skipTypedObject skips a typed object written by Java's writeTypedObject.
196+
// Null objects have flag=0. Non-null objects have flag!=0 followed by
197+
// variable-length data that we cannot skip without knowing the type.
198+
func skipTypedObject(
199+
p *parcel.Parcel,
200+
) error {
201+
flag, err := p.ReadInt32()
202+
if err != nil {
203+
return err
204+
}
205+
if flag != 0 {
206+
return fmt.Errorf("non-null typed object: cannot skip without known wire format")
207+
}
208+
return nil
209+
}

0 commit comments

Comments
 (0)