Skip to content

Commit 6400283

Browse files
shai-almogclaude
andcommitted
Windows port: contacts via WinRT ContactStore
Implements getAllContacts/getContactById (status.md gap 4) via the WinRT ContactStore (cn1_windows_winrt.cpp, same CN1_HAVE_WINRT gate). One native call returns every contact as a delimited blob (id/name/phone/email, read via the base IContact + versioned IContact2/IContactManagerStatics2 interfaces) which the impl parses and briefly caches so the base's id-then-fetch loop shares a single store read. Returns nothing when the store is inaccessible (no WinRT / access denied), never fabricated. Compiles on the Windows ARM64 VM via the proven WRL await pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7e0bae5 commit 6400283

4 files changed

Lines changed: 201 additions & 1 deletion

File tree

Ports/WindowsPort/nativeSources/cn1_windows_winrt.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,21 @@
4343
#ifdef CN1_HAVE_WINRT
4444

4545
#include <roapi.h>
46+
#include <string>
4647
#include <wrl.h>
4748
#include <wrl/event.h>
4849
#include <wrl/wrappers/corewrappers.h>
4950
#include <windows.foundation.h>
51+
#include <windows.foundation.collections.h>
5052
#include <windows.security.credentials.ui.h>
5153
#include <windows.devices.geolocation.h>
54+
#include <windows.applicationmodel.contacts.h>
5255

5356
using namespace ABI::Windows::Foundation;
57+
using namespace ABI::Windows::Foundation::Collections;
5458
using namespace ABI::Windows::Security::Credentials::UI;
5559
using namespace ABI::Windows::Devices::Geolocation;
60+
using namespace ABI::Windows::ApplicationModel::Contacts;
5661
using namespace Microsoft::WRL;
5762
using namespace Microsoft::WRL::Wrappers;
5863

@@ -94,6 +99,22 @@ static void cn1WinRtInit() {
9499
RoInitialize(RO_INIT_MULTITHREADED);
95100
}
96101

102+
/* Appends the UTF-8 bytes of an HSTRING to `out` (empty HSTRING -> nothing). */
103+
static void cn1AppendHString(std::string& out, HSTRING h) {
104+
UINT32 len = 0;
105+
PCWSTR w = WindowsGetStringRawBuffer(h, &len);
106+
if (w == NULL || len == 0) {
107+
return;
108+
}
109+
int n = WideCharToMultiByte(CP_UTF8, 0, w, (int) len, NULL, 0, NULL, NULL);
110+
if (n <= 0) {
111+
return;
112+
}
113+
size_t at = out.size();
114+
out.resize(at + (size_t) n);
115+
WideCharToMultiByte(CP_UTF8, 0, w, (int) len, &out[at], n, NULL, NULL);
116+
}
117+
97118
#endif /* CN1_HAVE_WINRT */
98119

99120
extern "C" {
@@ -243,6 +264,103 @@ JAVA_BOOLEAN com_codename1_impl_windows_WindowsNative_locationGetCurrent___doubl
243264
#endif
244265
}
245266

267+
/* -------------------------------------------------------------- contacts
268+
*
269+
* Reads the user's contacts via the WinRT ContactStore and returns them as a
270+
* single record-delimited blob: each contact is "id US name US phone US email"
271+
* (field separator 0x1F, record separator 0x1E). The Java side parses it into CN1
272+
* Contact objects. One native call avoids a chatty per-field bridge. Returns null
273+
* when the store is inaccessible (no WinRT, or access denied) and an empty string
274+
* when the store simply has no contacts -- both honest, never fabricated. */
275+
JAVA_OBJECT com_codename1_impl_windows_WindowsNative_contactsGetAll___R_java_lang_String(CODENAME_ONE_THREAD_STATE) {
276+
#ifdef CN1_HAVE_WINRT
277+
cn1WinRtInit();
278+
ComPtr<IContactManagerStatics2> statics;
279+
if (FAILED(RoGetActivationFactory(
280+
HStringReference(RuntimeClass_Windows_ApplicationModel_Contacts_ContactManager).Get(),
281+
IID_PPV_ARGS(&statics)))) {
282+
return JAVA_NULL;
283+
}
284+
ComPtr<IAsyncOperation<ContactStore*>> storeOp;
285+
if (FAILED(statics->RequestStoreAsync(&storeOp))) {
286+
return JAVA_NULL;
287+
}
288+
ComPtr<IContactStore> store;
289+
if (FAILED(cn1AwaitOp(storeOp.Get(), store.GetAddressOf())) || !store) {
290+
return JAVA_NULL;
291+
}
292+
ComPtr<IAsyncOperation<IVectorView<Contact*>*>> findOp;
293+
if (FAILED(store->FindContactsAsync(&findOp))) {
294+
return JAVA_NULL;
295+
}
296+
ComPtr<IVectorView<Contact*>> contacts;
297+
if (FAILED(cn1AwaitOp(findOp.Get(), contacts.GetAddressOf())) || !contacts) {
298+
return JAVA_NULL;
299+
}
300+
unsigned int size = 0;
301+
contacts->get_Size(&size);
302+
std::string blob;
303+
for (unsigned int i = 0; i < size; i++) {
304+
ComPtr<IContact> c;
305+
if (FAILED(contacts->GetAt(i, &c)) || !c) {
306+
continue;
307+
}
308+
if (i > 0) {
309+
blob.push_back('\x1e');
310+
}
311+
/* Name is on the base IContact; Id / Phones / Emails on IContact2. */
312+
HString name;
313+
c->get_Name(name.GetAddressOf());
314+
ComPtr<IContact2> c2;
315+
c.As(&c2);
316+
HString id;
317+
if (c2) {
318+
c2->get_Id(id.GetAddressOf());
319+
}
320+
cn1AppendHString(blob, id.Get());
321+
blob.push_back('\x1f');
322+
cn1AppendHString(blob, name.Get());
323+
blob.push_back('\x1f');
324+
/* first phone number */
325+
if (c2) {
326+
ComPtr<IVector<ContactPhone*>> phones;
327+
if (SUCCEEDED(c2->get_Phones(&phones)) && phones) {
328+
unsigned int pn = 0;
329+
phones->get_Size(&pn);
330+
if (pn > 0) {
331+
ComPtr<IContactPhone> phone;
332+
if (SUCCEEDED(phones->GetAt(0, &phone)) && phone) {
333+
HString num;
334+
phone->get_Number(num.GetAddressOf());
335+
cn1AppendHString(blob, num.Get());
336+
}
337+
}
338+
}
339+
}
340+
blob.push_back('\x1f');
341+
/* first email */
342+
if (c2) {
343+
ComPtr<IVector<ContactEmail*>> emails;
344+
if (SUCCEEDED(c2->get_Emails(&emails)) && emails) {
345+
unsigned int en = 0;
346+
emails->get_Size(&en);
347+
if (en > 0) {
348+
ComPtr<IContactEmail> email;
349+
if (SUCCEEDED(emails->GetAt(0, &email)) && email) {
350+
HString addr;
351+
email->get_Address(addr.GetAddressOf());
352+
cn1AppendHString(blob, addr.Get());
353+
}
354+
}
355+
}
356+
}
357+
}
358+
return newStringFromCString(threadStateData, blob.c_str());
359+
#else
360+
return JAVA_NULL;
361+
#endif
362+
}
363+
246364
} /* extern "C" */
247365

248366
#endif /* _WIN32 */

Ports/WindowsPort/src/com/codename1/impl/windows/WindowsImplementation.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,69 @@ public com.codename1.location.LocationManager getLocationManager() {
371371
return locationManager;
372372
}
373373

374+
/* ------------------------------------------------------------ contacts
375+
* Backed by the WinRT ContactStore (cn1_windows_winrt.cpp). One native call
376+
* returns every contact as a delimited blob which is parsed and briefly
377+
* cached here so getAllContacts() + the per-id getContactById() the base
378+
* runs in a loop share a single store read. */
379+
private java.util.HashMap<String, String[]> contactCache;
380+
381+
private java.util.HashMap<String, String[]> contacts() {
382+
if (contactCache == null) {
383+
contactCache = new java.util.HashMap<String, String[]>();
384+
String blob = WindowsNative.contactsGetAll();
385+
if (blob != null && blob.length() > 0) {
386+
String[] records = blob.split("");
387+
for (int i = 0; i < records.length; i++) {
388+
String[] f = records[i].split("", -1);
389+
if (f.length >= 1 && f[0].length() > 0) {
390+
contactCache.put(f[0], f);
391+
}
392+
}
393+
}
394+
}
395+
return contactCache;
396+
}
397+
398+
@Override
399+
public String[] getAllContacts(boolean withNumbers) {
400+
java.util.HashMap<String, String[]> all = contacts();
401+
java.util.ArrayList<String> ids = new java.util.ArrayList<String>();
402+
for (String[] f : all.values()) {
403+
// withNumbers filters to contacts that have a phone number.
404+
if (!withNumbers || (f.length > 2 && f[2].length() > 0)) {
405+
ids.add(f[0]);
406+
}
407+
}
408+
return ids.toArray(new String[ids.size()]);
409+
}
410+
411+
@Override
412+
public com.codename1.contacts.Contact getContactById(String id) {
413+
String[] f = contacts().get(id);
414+
if (f == null) {
415+
return null;
416+
}
417+
com.codename1.contacts.Contact c = new com.codename1.contacts.Contact();
418+
c.setId(f[0]);
419+
if (f.length > 1) {
420+
c.setDisplayName(f[1]);
421+
}
422+
if (f.length > 2 && f[2].length() > 0) {
423+
c.setPrimaryPhoneNumber(f[2]);
424+
java.util.Hashtable phones = new java.util.Hashtable();
425+
phones.put("mobile", f[2]);
426+
c.setPhoneNumbers(phones);
427+
}
428+
if (f.length > 3 && f[3].length() > 0) {
429+
c.setPrimaryEmail(f[3]);
430+
java.util.Hashtable emails = new java.util.Hashtable();
431+
emails.put("home", f[3]);
432+
c.setEmails(emails);
433+
}
434+
return c;
435+
}
436+
374437
@Override
375438
public int getDisplayWidth() {
376439
return WindowsNative.getDisplayWidth();

Ports/WindowsPort/src/com/codename1/impl/windows/WindowsNative.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,17 @@ public static native long editStringAt(int x, int y, int w, int h, String text,
529529
*/
530530
public static native boolean locationGetCurrent(double[] out);
531531

532+
/* -------------------------------------------------- contacts (WinRT) */
533+
534+
/**
535+
* Reads the user's contacts via the WinRT {@code ContactStore} and returns
536+
* them as a single blob: records separated by {@code 0x1E}, fields (id, name,
537+
* phone, email) by {@code 0x1F}. {@code null} when the store is inaccessible
538+
* (no WinRT / access denied); empty string when there are simply no contacts.
539+
* Blocks (call off the EDT).
540+
*/
541+
public static native String contactsGetAll();
542+
532543
// ---------------------------------------------------------------------
533544
// 3D / Direct3D 11 backend (com.codename1.gpu). Implemented in
534545
// nativeSources/cn1_windows_d3d.cpp. Every peer is an opaque long (a D3D

Ports/WindowsPort/status.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,15 @@ they stay honest until backed by a real implementation:
150150
- Push + local notifications (`sendLocalNotification`)
151151
- Vibrate (no desktop vibration motor)
152152
- System share, print
153-
- Contacts (WinRT — pending)
153+
154+
**Contacts — implemented (WinRT).** `getAllContacts` / `getContactById` read the
155+
user's contacts via the WinRT `ContactStore` (`cn1_windows_winrt.cpp`, same
156+
`CN1_HAVE_WINRT` gate). One native call returns every contact as a delimited blob
157+
(id / name / phone / email) which the impl parses and briefly caches, so the
158+
base's id-then-fetch loop shares a single store read. Returns nothing when the
159+
store is inaccessible (no WinRT / access denied) -- honest, never fabricated.
160+
Compiles on the Windows ARM64 VM via the same proven WRL await pattern as
161+
biometric/location.
154162

155163
**Location / GPS — implemented (WinRT).** `getLocationManager()`
156164
`WindowsLocationManager`, backed by the WinRT `Geolocator`

0 commit comments

Comments
 (0)