@capgo/capacitor-contacts 7.0.0
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/CapgoCapacitorContacts.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +884 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/contacts/CapacitorContactsPlugin.java +770 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +2437 -0
- package/dist/esm/definitions.d.ts +944 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +28 -0
- package/dist/esm/web.js +70 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +84 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +87 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapacitorContactsPlugin/CapacitorContactsPlugin.swift +515 -0
- package/ios/Tests/CapacitorContactsPluginTests/CapacitorContactsPluginTests.swift +9 -0
- package/package.json +89 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
package app.capgo.contacts;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.annotation.SuppressLint;
|
|
5
|
+
import android.content.ContentResolver;
|
|
6
|
+
import android.content.ContentUris;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import android.database.Cursor;
|
|
9
|
+
import android.net.Uri;
|
|
10
|
+
import android.provider.ContactsContract;
|
|
11
|
+
import android.provider.Settings;
|
|
12
|
+
import android.util.Base64;
|
|
13
|
+
import androidx.annotation.NonNull;
|
|
14
|
+
import com.getcapacitor.JSArray;
|
|
15
|
+
import com.getcapacitor.JSObject;
|
|
16
|
+
import com.getcapacitor.PermissionState;
|
|
17
|
+
import com.getcapacitor.Plugin;
|
|
18
|
+
import com.getcapacitor.PluginCall;
|
|
19
|
+
import com.getcapacitor.PluginMethod;
|
|
20
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
21
|
+
import com.getcapacitor.annotation.Permission;
|
|
22
|
+
import com.getcapacitor.annotation.PermissionCallback;
|
|
23
|
+
import java.io.ByteArrayOutputStream;
|
|
24
|
+
import java.io.IOException;
|
|
25
|
+
import java.io.InputStream;
|
|
26
|
+
import java.util.ArrayList;
|
|
27
|
+
import java.util.HashMap;
|
|
28
|
+
import java.util.HashSet;
|
|
29
|
+
import java.util.List;
|
|
30
|
+
import java.util.Map;
|
|
31
|
+
import java.util.Set;
|
|
32
|
+
|
|
33
|
+
@CapacitorPlugin(
|
|
34
|
+
name = "CapacitorContacts",
|
|
35
|
+
permissions = {
|
|
36
|
+
@Permission(alias = "readContacts", strings = { Manifest.permission.READ_CONTACTS }),
|
|
37
|
+
@Permission(alias = "writeContacts", strings = { Manifest.permission.WRITE_CONTACTS })
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
public class CapacitorContactsPlugin extends Plugin {
|
|
41
|
+
|
|
42
|
+
// MARK: - Implemented API surface
|
|
43
|
+
|
|
44
|
+
@PluginMethod
|
|
45
|
+
public void countContacts(PluginCall call) {
|
|
46
|
+
if (!hasReadPermission()) {
|
|
47
|
+
call.reject("READ_CONTACTS permission not granted.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
52
|
+
try (
|
|
53
|
+
Cursor cursor = resolver.query(
|
|
54
|
+
ContactsContract.Contacts.CONTENT_URI,
|
|
55
|
+
new String[] { ContactsContract.Contacts._ID },
|
|
56
|
+
null,
|
|
57
|
+
null,
|
|
58
|
+
null
|
|
59
|
+
)
|
|
60
|
+
) {
|
|
61
|
+
int count = cursor != null ? cursor.getCount() : 0;
|
|
62
|
+
JSObject result = new JSObject();
|
|
63
|
+
result.put("count", count);
|
|
64
|
+
call.resolve(result);
|
|
65
|
+
} catch (Exception ex) {
|
|
66
|
+
call.reject("Failed to count contacts.", null, ex);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@PluginMethod
|
|
71
|
+
public void getContacts(PluginCall call) {
|
|
72
|
+
if (!hasReadPermission()) {
|
|
73
|
+
call.reject("READ_CONTACTS permission not granted.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
JSObject options = call.getObject("options", new JSObject());
|
|
78
|
+
Integer limit = options.has("limit") ? options.getInteger("limit") : null;
|
|
79
|
+
Integer offset = options.has("offset") ? options.getInteger("offset") : null;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
List<ContactBuilder> builders = fetchContacts(limit, offset);
|
|
83
|
+
JSArray contacts = new JSArray();
|
|
84
|
+
for (ContactBuilder builder : builders) {
|
|
85
|
+
contacts.put(builder.toJSObject());
|
|
86
|
+
}
|
|
87
|
+
JSObject result = new JSObject();
|
|
88
|
+
result.put("contacts", contacts);
|
|
89
|
+
call.resolve(result);
|
|
90
|
+
} catch (Exception ex) {
|
|
91
|
+
call.reject("Failed to fetch contacts.", null, ex);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@PluginMethod
|
|
96
|
+
public void getContactById(PluginCall call) {
|
|
97
|
+
if (!hasReadPermission()) {
|
|
98
|
+
call.reject("READ_CONTACTS permission not granted.");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
JSObject options = call.getObject("options", new JSObject());
|
|
103
|
+
String identifier = options.getString("id");
|
|
104
|
+
if (identifier == null) {
|
|
105
|
+
call.reject("Missing contact identifier.");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
ContactBuilder builder = fetchContact(identifier);
|
|
111
|
+
if (builder == null) {
|
|
112
|
+
call.resolve(new JSObject().put("contact", null));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
JSObject result = new JSObject();
|
|
116
|
+
result.put("contact", builder.toJSObject());
|
|
117
|
+
call.resolve(result);
|
|
118
|
+
} catch (Exception ex) {
|
|
119
|
+
call.reject("Failed to fetch contact.", null, ex);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@PluginMethod
|
|
124
|
+
public void getAccounts(PluginCall call) {
|
|
125
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
126
|
+
JSArray accounts = new JSArray();
|
|
127
|
+
Set<String> seen = new HashSet<>();
|
|
128
|
+
|
|
129
|
+
try (
|
|
130
|
+
Cursor cursor = resolver.query(
|
|
131
|
+
ContactsContract.RawContacts.CONTENT_URI,
|
|
132
|
+
new String[] { ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE },
|
|
133
|
+
null,
|
|
134
|
+
null,
|
|
135
|
+
null
|
|
136
|
+
)
|
|
137
|
+
) {
|
|
138
|
+
if (cursor != null) {
|
|
139
|
+
while (cursor.moveToNext()) {
|
|
140
|
+
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_NAME));
|
|
141
|
+
String type = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_TYPE));
|
|
142
|
+
String key = (name == null ? "" : name) + "|" + (type == null ? "" : type);
|
|
143
|
+
if (seen.add(key)) {
|
|
144
|
+
JSObject account = new JSObject();
|
|
145
|
+
account.put("name", name);
|
|
146
|
+
account.put("type", type);
|
|
147
|
+
accounts.put(account);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
call.resolve(new JSObject().put("accounts", accounts));
|
|
152
|
+
} catch (Exception ex) {
|
|
153
|
+
call.reject("Failed to fetch accounts.", null, ex);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@PluginMethod
|
|
158
|
+
public void isSupported(PluginCall call) {
|
|
159
|
+
call.resolve(new JSObject().put("isSupported", true));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@PluginMethod
|
|
163
|
+
public void isAvailable(PluginCall call) {
|
|
164
|
+
call.resolve(new JSObject().put("isAvailable", true));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@PluginMethod
|
|
168
|
+
public void openSettings(PluginCall call) {
|
|
169
|
+
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
170
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
171
|
+
Uri uri = Uri.fromParts("package", getContext().getPackageName(), null);
|
|
172
|
+
intent.setData(uri);
|
|
173
|
+
getContext().startActivity(intent);
|
|
174
|
+
call.resolve();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@PluginMethod
|
|
178
|
+
public void checkPermissions(PluginCall call) {
|
|
179
|
+
JSObject status = buildPermissionStatus();
|
|
180
|
+
call.resolve(status);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@PluginMethod
|
|
184
|
+
public void requestPermissions(PluginCall call) {
|
|
185
|
+
// In Capacitor 7, the permission system works differently
|
|
186
|
+
// We just need to request the permissions defined in the plugin annotation
|
|
187
|
+
requestPermissionForAlias("readContacts", call, "handleRequestPermissions");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@PermissionCallback
|
|
191
|
+
public void handleRequestPermissions(PluginCall call) {
|
|
192
|
+
JSObject status = buildPermissionStatus();
|
|
193
|
+
call.resolve(status);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// MARK: - Not yet implemented operations
|
|
197
|
+
|
|
198
|
+
private void notImplemented(PluginCall call) {
|
|
199
|
+
call.reject("Method not implemented yet.");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@PluginMethod
|
|
203
|
+
public void createContact(PluginCall call) {
|
|
204
|
+
notImplemented(call);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@PluginMethod
|
|
208
|
+
public void createGroup(PluginCall call) {
|
|
209
|
+
notImplemented(call);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@PluginMethod
|
|
213
|
+
public void deleteContactById(PluginCall call) {
|
|
214
|
+
notImplemented(call);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@PluginMethod
|
|
218
|
+
public void deleteGroupById(PluginCall call) {
|
|
219
|
+
notImplemented(call);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@PluginMethod
|
|
223
|
+
public void displayContactById(PluginCall call) {
|
|
224
|
+
notImplemented(call);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@PluginMethod
|
|
228
|
+
public void displayCreateContact(PluginCall call) {
|
|
229
|
+
notImplemented(call);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@PluginMethod
|
|
233
|
+
public void displayUpdateContactById(PluginCall call) {
|
|
234
|
+
notImplemented(call);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@PluginMethod
|
|
238
|
+
public void getGroupById(PluginCall call) {
|
|
239
|
+
notImplemented(call);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@PluginMethod
|
|
243
|
+
public void getGroups(PluginCall call) {
|
|
244
|
+
notImplemented(call);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@PluginMethod
|
|
248
|
+
public void pickContact(PluginCall call) {
|
|
249
|
+
notImplemented(call);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@PluginMethod
|
|
253
|
+
public void pickContacts(PluginCall call) {
|
|
254
|
+
notImplemented(call);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@PluginMethod
|
|
258
|
+
public void updateContactById(PluginCall call) {
|
|
259
|
+
notImplemented(call);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// MARK: - Permissions helpers
|
|
263
|
+
|
|
264
|
+
private boolean hasReadPermission() {
|
|
265
|
+
return getPermissionState("readContacts") == PermissionState.GRANTED;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private JSObject buildPermissionStatus() {
|
|
269
|
+
JSObject status = new JSObject();
|
|
270
|
+
status.put("readContacts", mapPermissionState(getPermissionState("readContacts")));
|
|
271
|
+
status.put("writeContacts", mapPermissionState(getPermissionState("writeContacts")));
|
|
272
|
+
return status;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private String mapPermissionState(PermissionState state) {
|
|
276
|
+
if (state == null) {
|
|
277
|
+
return "prompt";
|
|
278
|
+
}
|
|
279
|
+
switch (state) {
|
|
280
|
+
case GRANTED:
|
|
281
|
+
return "granted";
|
|
282
|
+
case DENIED:
|
|
283
|
+
return "denied";
|
|
284
|
+
case PROMPT_WITH_RATIONALE:
|
|
285
|
+
return "prompt-with-rationale";
|
|
286
|
+
default:
|
|
287
|
+
return "prompt";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// MARK: - Contact access helpers
|
|
292
|
+
|
|
293
|
+
private List<ContactBuilder> fetchContacts(Integer limit, Integer offset) {
|
|
294
|
+
List<ContactBuilder> builders = new ArrayList<>();
|
|
295
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
296
|
+
|
|
297
|
+
Uri queryUri = ContactsContract.Contacts.CONTENT_URI;
|
|
298
|
+
if (limit != null) {
|
|
299
|
+
android.net.Uri.Builder builder = ContactsContract.Contacts.CONTENT_URI.buildUpon();
|
|
300
|
+
builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, String.valueOf(limit));
|
|
301
|
+
if (offset != null) {
|
|
302
|
+
// START_PARAM_KEY was removed, use "offset" directly
|
|
303
|
+
builder.appendQueryParameter("offset", String.valueOf(offset));
|
|
304
|
+
}
|
|
305
|
+
queryUri = builder.build();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try (
|
|
309
|
+
Cursor cursor = resolver.query(
|
|
310
|
+
queryUri,
|
|
311
|
+
new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY },
|
|
312
|
+
null,
|
|
313
|
+
null,
|
|
314
|
+
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
if (cursor == null) {
|
|
318
|
+
return builders;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
while (cursor.moveToNext()) {
|
|
322
|
+
String id = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID));
|
|
323
|
+
ContactBuilder contact = fetchContact(id);
|
|
324
|
+
if (contact != null) {
|
|
325
|
+
contact.fullName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
|
|
326
|
+
builders.add(contact);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return builders;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private ContactBuilder fetchContact(@NonNull String contactId) {
|
|
335
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
336
|
+
ContactBuilder builder = new ContactBuilder(contactId);
|
|
337
|
+
|
|
338
|
+
// Structured name, emails, phones, etc.
|
|
339
|
+
try (
|
|
340
|
+
Cursor dataCursor = resolver.query(
|
|
341
|
+
ContactsContract.Data.CONTENT_URI,
|
|
342
|
+
null,
|
|
343
|
+
ContactsContract.Data.CONTACT_ID + " = ?",
|
|
344
|
+
new String[] { contactId },
|
|
345
|
+
null
|
|
346
|
+
)
|
|
347
|
+
) {
|
|
348
|
+
if (dataCursor == null) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
while (dataCursor.moveToNext()) {
|
|
353
|
+
String mimeType = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE));
|
|
354
|
+
switch (mimeType) {
|
|
355
|
+
case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
|
|
356
|
+
builder.givenName = dataCursor.getString(
|
|
357
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
|
|
358
|
+
);
|
|
359
|
+
builder.familyName = dataCursor.getString(
|
|
360
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
|
|
361
|
+
);
|
|
362
|
+
builder.middleName = dataCursor.getString(
|
|
363
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)
|
|
364
|
+
);
|
|
365
|
+
builder.namePrefix = dataCursor.getString(
|
|
366
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.PREFIX)
|
|
367
|
+
);
|
|
368
|
+
builder.nameSuffix = dataCursor.getString(
|
|
369
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredName.SUFFIX)
|
|
370
|
+
);
|
|
371
|
+
break;
|
|
372
|
+
case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:
|
|
373
|
+
builder.addEmail(
|
|
374
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS)),
|
|
375
|
+
dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.TYPE)),
|
|
376
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.LABEL)),
|
|
377
|
+
dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.IS_PRIMARY)) == 1
|
|
378
|
+
);
|
|
379
|
+
break;
|
|
380
|
+
case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
|
|
381
|
+
builder.addPhone(
|
|
382
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)),
|
|
383
|
+
dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.TYPE)),
|
|
384
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.LABEL)),
|
|
385
|
+
dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.IS_PRIMARY)) == 1
|
|
386
|
+
);
|
|
387
|
+
break;
|
|
388
|
+
case ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE:
|
|
389
|
+
builder.addPostalAddress(
|
|
390
|
+
dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)),
|
|
391
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.LABEL)),
|
|
392
|
+
dataCursor.getString(
|
|
393
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.STREET)
|
|
394
|
+
),
|
|
395
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.CITY)),
|
|
396
|
+
dataCursor.getString(
|
|
397
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.REGION)
|
|
398
|
+
),
|
|
399
|
+
dataCursor.getString(
|
|
400
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)
|
|
401
|
+
),
|
|
402
|
+
dataCursor.getString(
|
|
403
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)
|
|
404
|
+
),
|
|
405
|
+
dataCursor.getString(
|
|
406
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD)
|
|
407
|
+
),
|
|
408
|
+
dataCursor.getInt(
|
|
409
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.StructuredPostal.IS_PRIMARY)
|
|
410
|
+
) ==
|
|
411
|
+
1
|
|
412
|
+
);
|
|
413
|
+
break;
|
|
414
|
+
case ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE:
|
|
415
|
+
builder.addUrlAddress(
|
|
416
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.URL)),
|
|
417
|
+
dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.TYPE)),
|
|
418
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Website.LABEL))
|
|
419
|
+
);
|
|
420
|
+
break;
|
|
421
|
+
case ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE:
|
|
422
|
+
builder.organizationName = dataCursor.getString(
|
|
423
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Organization.COMPANY)
|
|
424
|
+
);
|
|
425
|
+
builder.jobTitle = dataCursor.getString(
|
|
426
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Organization.TITLE)
|
|
427
|
+
);
|
|
428
|
+
break;
|
|
429
|
+
case ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE:
|
|
430
|
+
builder.note = dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Note.NOTE));
|
|
431
|
+
break;
|
|
432
|
+
case ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE:
|
|
433
|
+
int eventType = dataCursor.getInt(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.TYPE));
|
|
434
|
+
if (eventType == ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY) {
|
|
435
|
+
builder.setBirthday(
|
|
436
|
+
dataCursor.getString(dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Event.START_DATE))
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
case ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE:
|
|
441
|
+
long groupId = dataCursor.getLong(
|
|
442
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)
|
|
443
|
+
);
|
|
444
|
+
builder.addGroupId(String.valueOf(groupId));
|
|
445
|
+
break;
|
|
446
|
+
case ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE:
|
|
447
|
+
byte[] photoData = dataCursor.getBlob(
|
|
448
|
+
dataCursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Photo.PHOTO)
|
|
449
|
+
);
|
|
450
|
+
if (photoData != null) {
|
|
451
|
+
builder.photoBase64 = Base64.encodeToString(photoData, Base64.NO_WRAP);
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Account information
|
|
461
|
+
try (
|
|
462
|
+
Cursor rawCursor = resolver.query(
|
|
463
|
+
ContactsContract.RawContacts.CONTENT_URI,
|
|
464
|
+
new String[] { ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE },
|
|
465
|
+
ContactsContract.RawContacts.CONTACT_ID + " = ?",
|
|
466
|
+
new String[] { contactId },
|
|
467
|
+
null
|
|
468
|
+
)
|
|
469
|
+
) {
|
|
470
|
+
if (rawCursor != null && rawCursor.moveToFirst()) {
|
|
471
|
+
String accountName = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_NAME));
|
|
472
|
+
String accountType = rawCursor.getString(rawCursor.getColumnIndexOrThrow(ContactsContract.RawContacts.ACCOUNT_TYPE));
|
|
473
|
+
builder.accountName = accountName;
|
|
474
|
+
builder.accountType = accountType;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (builder.fullName == null) {
|
|
479
|
+
builder.fullName = resolveDisplayName(contactId);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return builder;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private String resolveDisplayName(String contactId) {
|
|
486
|
+
ContentResolver resolver = getContext().getContentResolver();
|
|
487
|
+
try (
|
|
488
|
+
Cursor cursor = resolver.query(
|
|
489
|
+
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(contactId)),
|
|
490
|
+
new String[] { ContactsContract.Contacts.DISPLAY_NAME_PRIMARY },
|
|
491
|
+
null,
|
|
492
|
+
null,
|
|
493
|
+
null
|
|
494
|
+
)
|
|
495
|
+
) {
|
|
496
|
+
if (cursor != null && cursor.moveToFirst()) {
|
|
497
|
+
return cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// MARK: - Contact builder helper
|
|
504
|
+
|
|
505
|
+
private static class ContactBuilder {
|
|
506
|
+
|
|
507
|
+
final String id;
|
|
508
|
+
String givenName;
|
|
509
|
+
String familyName;
|
|
510
|
+
String middleName;
|
|
511
|
+
String namePrefix;
|
|
512
|
+
String nameSuffix;
|
|
513
|
+
String organizationName;
|
|
514
|
+
String jobTitle;
|
|
515
|
+
String note;
|
|
516
|
+
String fullName;
|
|
517
|
+
String photoBase64;
|
|
518
|
+
String accountName;
|
|
519
|
+
String accountType;
|
|
520
|
+
Integer birthdayYear;
|
|
521
|
+
Integer birthdayMonth;
|
|
522
|
+
Integer birthdayDay;
|
|
523
|
+
final JSArray groupIds = new JSArray();
|
|
524
|
+
final JSArray emailAddresses = new JSArray();
|
|
525
|
+
final JSArray phoneNumbers = new JSArray();
|
|
526
|
+
final JSArray postalAddresses = new JSArray();
|
|
527
|
+
final JSArray urlAddresses = new JSArray();
|
|
528
|
+
|
|
529
|
+
ContactBuilder(String id) {
|
|
530
|
+
this.id = id;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
void addEmail(String value, int type, String label, boolean isPrimary) {
|
|
534
|
+
if (value == null) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
JSObject email = new JSObject();
|
|
538
|
+
email.put("value", value);
|
|
539
|
+
email.put("type", mapEmailType(type));
|
|
540
|
+
if (type == ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM && label != null) {
|
|
541
|
+
email.put("label", label);
|
|
542
|
+
}
|
|
543
|
+
email.put("isPrimary", isPrimary);
|
|
544
|
+
emailAddresses.put(email);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
void addPhone(String value, int type, String label, boolean isPrimary) {
|
|
548
|
+
if (value == null) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
JSObject phone = new JSObject();
|
|
552
|
+
phone.put("value", value);
|
|
553
|
+
phone.put("type", mapPhoneType(type));
|
|
554
|
+
if (type == ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM && label != null) {
|
|
555
|
+
phone.put("label", label);
|
|
556
|
+
}
|
|
557
|
+
phone.put("isPrimary", isPrimary);
|
|
558
|
+
phoneNumbers.put(phone);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
void addPostalAddress(
|
|
562
|
+
int type,
|
|
563
|
+
String label,
|
|
564
|
+
String street,
|
|
565
|
+
String city,
|
|
566
|
+
String state,
|
|
567
|
+
String postalCode,
|
|
568
|
+
String country,
|
|
569
|
+
String neighborhood,
|
|
570
|
+
boolean isPrimary
|
|
571
|
+
) {
|
|
572
|
+
JSObject address = new JSObject();
|
|
573
|
+
address.put("street", street);
|
|
574
|
+
address.put("city", city);
|
|
575
|
+
address.put("state", state);
|
|
576
|
+
address.put("postalCode", postalCode);
|
|
577
|
+
address.put("country", country);
|
|
578
|
+
address.put("neighborhood", neighborhood);
|
|
579
|
+
address.put("formatted", buildFormattedAddress(street, city, state, postalCode, country));
|
|
580
|
+
address.put("isoCountryCode", null);
|
|
581
|
+
address.put("isPrimary", isPrimary);
|
|
582
|
+
address.put("type", mapPostalType(type));
|
|
583
|
+
if (type == ContactsContract.CommonDataKinds.StructuredPostal.TYPE_CUSTOM && label != null) {
|
|
584
|
+
address.put("label", label);
|
|
585
|
+
}
|
|
586
|
+
postalAddresses.put(address);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
void addUrlAddress(String value, int type, String label) {
|
|
590
|
+
if (value == null) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
JSObject url = new JSObject();
|
|
594
|
+
url.put("value", value);
|
|
595
|
+
url.put("type", mapUrlType(type));
|
|
596
|
+
if (type == ContactsContract.CommonDataKinds.Website.TYPE_CUSTOM && label != null) {
|
|
597
|
+
url.put("label", label);
|
|
598
|
+
}
|
|
599
|
+
urlAddresses.put(url);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
void addGroupId(String groupId) {
|
|
603
|
+
if (groupId == null) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
groupIds.put(groupId);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
void setBirthday(String startDate) {
|
|
610
|
+
if (startDate == null || startDate.isEmpty()) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
String[] parts = startDate.split("-");
|
|
614
|
+
if (parts.length >= 2) {
|
|
615
|
+
birthdayMonth = safeParse(parts[0]);
|
|
616
|
+
birthdayDay = safeParse(parts[1]);
|
|
617
|
+
}
|
|
618
|
+
if (parts.length >= 3) {
|
|
619
|
+
birthdayYear = safeParse(parts[2]);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
JSObject toJSObject() {
|
|
624
|
+
JSObject contact = new JSObject();
|
|
625
|
+
contact.put("id", id);
|
|
626
|
+
contact.put("givenName", givenName);
|
|
627
|
+
contact.put("familyName", familyName);
|
|
628
|
+
contact.put("middleName", middleName);
|
|
629
|
+
contact.put("namePrefix", namePrefix);
|
|
630
|
+
contact.put("nameSuffix", nameSuffix);
|
|
631
|
+
contact.put("organizationName", organizationName);
|
|
632
|
+
contact.put("jobTitle", jobTitle);
|
|
633
|
+
contact.put("note", note);
|
|
634
|
+
contact.put("fullName", fullName);
|
|
635
|
+
contact.put("photo", photoBase64);
|
|
636
|
+
contact.put("groupIds", groupIds);
|
|
637
|
+
contact.put("emailAddresses", emailAddresses);
|
|
638
|
+
contact.put("phoneNumbers", phoneNumbers);
|
|
639
|
+
contact.put("postalAddresses", postalAddresses);
|
|
640
|
+
contact.put("urlAddresses", urlAddresses);
|
|
641
|
+
|
|
642
|
+
if (birthdayYear != null || birthdayMonth != null || birthdayDay != null) {
|
|
643
|
+
JSObject birthday = new JSObject();
|
|
644
|
+
if (birthdayDay != null) birthday.put("day", birthdayDay);
|
|
645
|
+
if (birthdayMonth != null) birthday.put("month", birthdayMonth);
|
|
646
|
+
if (birthdayYear != null) birthday.put("year", birthdayYear);
|
|
647
|
+
contact.put("birthday", birthday);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (accountName != null || accountType != null) {
|
|
651
|
+
JSObject account = new JSObject();
|
|
652
|
+
account.put("name", accountName);
|
|
653
|
+
account.put("type", accountType);
|
|
654
|
+
contact.put("account", account);
|
|
655
|
+
} else {
|
|
656
|
+
contact.put("account", null);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return contact;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
private static Integer safeParse(String value) {
|
|
663
|
+
try {
|
|
664
|
+
return Integer.parseInt(value);
|
|
665
|
+
} catch (Exception ex) {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private static String buildFormattedAddress(String street, String city, String state, String postalCode, String country) {
|
|
671
|
+
StringBuilder builder = new StringBuilder();
|
|
672
|
+
if (street != null && !street.isEmpty()) builder.append(street).append('\n');
|
|
673
|
+
if (city != null && !city.isEmpty()) builder.append(city);
|
|
674
|
+
if (state != null && !state.isEmpty()) builder.append(builder.length() > 0 ? ", " : "").append(state);
|
|
675
|
+
if (postalCode != null && !postalCode.isEmpty()) builder.append(' ').append(postalCode);
|
|
676
|
+
if (country != null && !country.isEmpty()) builder.append(builder.length() > 0 ? "\n" : "").append(country);
|
|
677
|
+
return builder.toString();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private static String mapEmailType(int type) {
|
|
681
|
+
switch (type) {
|
|
682
|
+
case ContactsContract.CommonDataKinds.Email.TYPE_HOME:
|
|
683
|
+
return "HOME";
|
|
684
|
+
case ContactsContract.CommonDataKinds.Email.TYPE_WORK:
|
|
685
|
+
return "WORK";
|
|
686
|
+
case ContactsContract.CommonDataKinds.Email.TYPE_OTHER:
|
|
687
|
+
return "OTHER";
|
|
688
|
+
case ContactsContract.CommonDataKinds.Email.TYPE_MOBILE:
|
|
689
|
+
return "MOBILE";
|
|
690
|
+
case ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM:
|
|
691
|
+
return "CUSTOM";
|
|
692
|
+
// TYPE_MAIN constant was removed from Android SDK
|
|
693
|
+
default:
|
|
694
|
+
return "OTHER";
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private static String mapPhoneType(int type) {
|
|
699
|
+
switch (type) {
|
|
700
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_HOME:
|
|
701
|
+
return "HOME";
|
|
702
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_WORK:
|
|
703
|
+
return "WORK";
|
|
704
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE:
|
|
705
|
+
return "MOBILE";
|
|
706
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_MAIN:
|
|
707
|
+
return "MAIN";
|
|
708
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME:
|
|
709
|
+
return "HOME_FAX";
|
|
710
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK:
|
|
711
|
+
return "WORK_FAX";
|
|
712
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX:
|
|
713
|
+
return "OTHER_FAX";
|
|
714
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_PAGER:
|
|
715
|
+
return "PAGER";
|
|
716
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_CAR:
|
|
717
|
+
return "CAR";
|
|
718
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK:
|
|
719
|
+
return "CALLBACK";
|
|
720
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN:
|
|
721
|
+
return "COMPANY_MAIN";
|
|
722
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT:
|
|
723
|
+
return "ASSISTANT";
|
|
724
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER:
|
|
725
|
+
return "OTHER";
|
|
726
|
+
case ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM:
|
|
727
|
+
return "CUSTOM";
|
|
728
|
+
default:
|
|
729
|
+
return "OTHER";
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private static String mapPostalType(int type) {
|
|
734
|
+
switch (type) {
|
|
735
|
+
case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME:
|
|
736
|
+
return "HOME";
|
|
737
|
+
case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK:
|
|
738
|
+
return "WORK";
|
|
739
|
+
case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER:
|
|
740
|
+
return "OTHER";
|
|
741
|
+
case ContactsContract.CommonDataKinds.StructuredPostal.TYPE_CUSTOM:
|
|
742
|
+
return "CUSTOM";
|
|
743
|
+
default:
|
|
744
|
+
return "OTHER";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private static String mapUrlType(int type) {
|
|
749
|
+
switch (type) {
|
|
750
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_HOME:
|
|
751
|
+
return "HOME";
|
|
752
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_WORK:
|
|
753
|
+
return "WORK";
|
|
754
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_BLOG:
|
|
755
|
+
return "BLOG";
|
|
756
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_PROFILE:
|
|
757
|
+
return "PROFILE";
|
|
758
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_FTP:
|
|
759
|
+
return "FTP";
|
|
760
|
+
// TYPE_HOME_PAGE and TYPE_SCHOOL constants were removed from Android SDK
|
|
761
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_OTHER:
|
|
762
|
+
return "OTHER";
|
|
763
|
+
case ContactsContract.CommonDataKinds.Website.TYPE_CUSTOM:
|
|
764
|
+
return "CUSTOM";
|
|
765
|
+
default:
|
|
766
|
+
return "OTHER";
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|