@aalzehla/capacitor-contacts 0.0.5 → 8.0.1

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 (45) hide show
  1. package/AalzehlaCapacitorContacts.podspec +2 -2
  2. package/README.md +69 -45
  3. package/android/build.gradle +15 -12
  4. package/android/src/main/java/getcapacitor/community/contacts/BiMap.java +45 -0
  5. package/android/src/main/java/getcapacitor/community/contacts/ContactPayload.java +286 -0
  6. package/android/src/main/java/getcapacitor/community/contacts/Contacts.java +384 -0
  7. package/android/src/main/java/getcapacitor/community/contacts/ContactsPlugin.java +255 -0
  8. package/android/src/main/java/getcapacitor/community/contacts/CreateContactInput.java +233 -0
  9. package/android/src/main/java/getcapacitor/community/contacts/GetContactsProjectionInput.java +214 -0
  10. package/dist/esm/definitions.d.ts +285 -19
  11. package/dist/esm/definitions.js +47 -1
  12. package/dist/esm/definitions.js.map +1 -1
  13. package/dist/esm/index.d.ts +3 -3
  14. package/dist/esm/index.js +3 -3
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/web.d.ts +9 -7
  17. package/dist/esm/web.js +21 -7
  18. package/dist/esm/web.js.map +1 -1
  19. package/dist/plugin.cjs.js +73 -14
  20. package/dist/plugin.cjs.js.map +1 -1
  21. package/dist/plugin.js +74 -15
  22. package/dist/plugin.js.map +1 -1
  23. package/ios/Plugin/BiMap.swift +42 -0
  24. package/ios/Plugin/ContactPayload.swift +227 -0
  25. package/ios/Plugin/Contacts.swift +227 -0
  26. package/ios/Plugin/ContactsPlugin.h +10 -0
  27. package/ios/Plugin/ContactsPlugin.m +14 -0
  28. package/ios/Plugin/ContactsPlugin.swift +221 -0
  29. package/ios/Plugin/CreateContactInput.swift +187 -0
  30. package/ios/Plugin/GetContactsProjectionInput.swift +119 -0
  31. package/ios/Plugin/Info.plist +24 -0
  32. package/package.json +32 -29
  33. package/Package.swift +0 -28
  34. package/android/src/main/java/com/aalzehla/capacitor/contacts/CapacitorContactsPlugin.java +0 -177
  35. package/android/src/main/java/com/aalzehla/capacitor/contacts/ContactDataExtractorVisitor.java +0 -122
  36. package/android/src/main/java/com/aalzehla/capacitor/contacts/ContactExtractorVisitor.java +0 -31
  37. package/android/src/main/java/com/aalzehla/capacitor/contacts/PluginContactFields.java +0 -17
  38. package/android/src/main/java/com/aalzehla/capacitor/contacts/contentQuery/ContentQuery.java +0 -83
  39. package/android/src/main/java/com/aalzehla/capacitor/contacts/contentQuery/ContentQueryService.java +0 -60
  40. package/android/src/main/java/com/aalzehla/capacitor/contacts/utils/Utils.java +0 -19
  41. package/android/src/main/java/com/aalzehla/capacitor/contacts/utils/Visitable.java +0 -5
  42. package/android/src/main/java/com/aalzehla/capacitor/contacts/utils/Visitor.java +0 -5
  43. package/dist/docs.json +0 -145
  44. package/ios/Sources/CapacitorContactsPlugin/CapacitorContactsPlugin.swift +0 -168
  45. package/ios/Tests/CapacitorContactsPluginTests/CapacitorContactsPluginTests.swift +0 -15
@@ -0,0 +1,384 @@
1
+ package getcapacitor.community.contacts;
2
+
3
+ import android.app.Activity;
4
+ import android.content.ContentProviderOperation;
5
+ import android.content.ContentProviderResult;
6
+ import android.content.ContentResolver;
7
+ import android.database.Cursor;
8
+ import android.net.Uri;
9
+ import android.provider.ContactsContract;
10
+ import android.provider.ContactsContract.CommonDataKinds.Email;
11
+ import android.provider.ContactsContract.CommonDataKinds.Event;
12
+ import android.provider.ContactsContract.CommonDataKinds.Note;
13
+ import android.provider.ContactsContract.CommonDataKinds.Organization;
14
+ import android.provider.ContactsContract.CommonDataKinds.Phone;
15
+ import android.provider.ContactsContract.CommonDataKinds.StructuredName;
16
+ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
17
+ import android.provider.ContactsContract.CommonDataKinds.Website;
18
+ import android.provider.ContactsContract.RawContacts;
19
+ import android.util.Base64;
20
+ import androidx.annotation.NonNull;
21
+ import androidx.annotation.Nullable;
22
+ import java.util.ArrayList;
23
+ import java.util.HashMap;
24
+
25
+ public class Contacts {
26
+
27
+ private final Activity mActivity;
28
+
29
+ public static BiMap<String, Integer> phoneTypeMap = new BiMap<String, Integer>(
30
+ new HashMap<String, Integer>() {
31
+ {
32
+ // Home / Work
33
+ put("home", Phone.TYPE_HOME);
34
+ put("work", Phone.TYPE_WORK);
35
+ // Other
36
+ put("other", Phone.TYPE_OTHER);
37
+ // Custom
38
+ put("custom", Phone.TYPE_CUSTOM);
39
+ // Phone specific
40
+ put("main", Phone.TYPE_MAIN);
41
+ put("mobile", Phone.TYPE_MOBILE);
42
+ put("pager", Phone.TYPE_PAGER);
43
+ put("fax_work", Phone.TYPE_FAX_WORK);
44
+ put("fax_home", Phone.TYPE_FAX_HOME);
45
+ put("fax_other", Phone.TYPE_OTHER_FAX);
46
+ //
47
+ //
48
+ // Android only:
49
+ // Phone specific
50
+ put("callback", Phone.TYPE_CALLBACK);
51
+ put("car", Phone.TYPE_CAR);
52
+ put("company_main", Phone.TYPE_COMPANY_MAIN);
53
+ put("isdn", Phone.TYPE_ISDN);
54
+ put("radio", Phone.TYPE_RADIO);
55
+ put("telex", Phone.TYPE_TELEX);
56
+ put("tty_tdd", Phone.TYPE_TTY_TDD);
57
+ put("work_mobile", Phone.TYPE_WORK_MOBILE);
58
+ put("work_pager", Phone.TYPE_WORK_PAGER);
59
+ put("assistant", Phone.TYPE_ASSISTANT);
60
+ put("mms", Phone.TYPE_MMS);
61
+ }
62
+ },
63
+ "custom",
64
+ Phone.TYPE_CUSTOM
65
+ );
66
+
67
+ public static BiMap<String, Integer> emailTypeMap = new BiMap<String, Integer>(
68
+ new HashMap<String, Integer>() {
69
+ {
70
+ // Home / Work
71
+ put("home", Email.TYPE_HOME);
72
+ put("work", Email.TYPE_WORK);
73
+ // Other
74
+ put("other", Email.TYPE_OTHER);
75
+ // Custom
76
+ put("custom", Email.TYPE_CUSTOM);
77
+ //
78
+ //
79
+ // Android only:
80
+ // Email specific
81
+ put("mobile", Email.TYPE_MOBILE);
82
+ }
83
+ },
84
+ "custom",
85
+ Email.TYPE_CUSTOM
86
+ );
87
+
88
+ public static BiMap<String, Integer> postalAddressTypeMap = new BiMap<String, Integer>(
89
+ new HashMap<String, Integer>() {
90
+ {
91
+ // Home / Work
92
+ put("home", StructuredPostal.TYPE_HOME);
93
+ put("work", StructuredPostal.TYPE_WORK);
94
+ // Other
95
+ put("other", StructuredPostal.TYPE_OTHER);
96
+ // Custom
97
+ put("custom", StructuredPostal.TYPE_CUSTOM);
98
+ }
99
+ },
100
+ "custom",
101
+ StructuredPostal.TYPE_CUSTOM
102
+ );
103
+
104
+ Contacts(Activity activity) {
105
+ this.mActivity = activity;
106
+ }
107
+
108
+ public ContactPayload getContact(@NonNull String contactId, GetContactsProjectionInput projectionInput) {
109
+ String[] projection = projectionInput.getProjection();
110
+
111
+ String selection = ContactsContract.RawContacts.CONTACT_ID + " = ?";
112
+ String[] selectionArgs = new String[] { contactId };
113
+
114
+ ContentResolver cr = this.mActivity.getContentResolver();
115
+ Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, null);
116
+
117
+ if (cursor != null && cursor.getCount() > 0) {
118
+ ContactPayload contact = new ContactPayload(contactId);
119
+
120
+ while (cursor.moveToNext()) {
121
+ contact.fillDataByCursor(cursor);
122
+ }
123
+ cursor.close();
124
+
125
+ return contact;
126
+ }
127
+
128
+ if (cursor != null) {
129
+ cursor.close();
130
+ }
131
+
132
+ return null;
133
+ }
134
+
135
+ public HashMap<String, ContactPayload> getContacts(GetContactsProjectionInput projectionInput) {
136
+ String[] projection = projectionInput.getProjection();
137
+
138
+ // String[] selectionArgs = projectionInput.getSelectionArgs();
139
+
140
+ // String selection = GetContactsProjectionInput.getSelection(selectionArgs);
141
+
142
+ HashMap<String, ContactPayload> contacts = new HashMap<>();
143
+
144
+ ContentResolver cr = this.mActivity.getContentResolver();
145
+ // Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI, projection, selectionArgs.length > 0 ? selection : null, selectionArgs.length > 0 ? selectionArgs : null, null);
146
+ Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, projection, null, null, null);
147
+
148
+ if (cursor != null && cursor.getCount() > 0) {
149
+ while (cursor.moveToNext()) {
150
+ // String _id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
151
+ String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID));
152
+
153
+ ContactPayload contact;
154
+
155
+ if (!contacts.containsKey(contactId)) {
156
+ contact = new ContactPayload(contactId);
157
+ contacts.put(contactId, contact);
158
+ } else {
159
+ contact = contacts.get(contactId);
160
+ }
161
+
162
+ if (contact != null) {
163
+ contact.fillDataByCursor(cursor);
164
+ }
165
+ }
166
+ }
167
+
168
+ if (cursor != null) {
169
+ cursor.close();
170
+ }
171
+
172
+ return contacts;
173
+ }
174
+
175
+ public String createContact(CreateContactInput contactInput) {
176
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
177
+ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
178
+ .withValue(RawContacts.ACCOUNT_TYPE, null)
179
+ .withValue(RawContacts.ACCOUNT_NAME, null);
180
+ ops.add(op.build());
181
+
182
+ // Name
183
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
184
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
185
+ // Add to this key
186
+ .withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
187
+ // Data
188
+ // .withValue(StructuredName.DISPLAY_NAME, name)
189
+ .withValue(StructuredName.GIVEN_NAME, contactInput.nameGiven)
190
+ .withValue(StructuredName.MIDDLE_NAME, contactInput.nameMiddle)
191
+ .withValue(StructuredName.FAMILY_NAME, contactInput.nameFamily)
192
+ .withValue(StructuredName.PREFIX, contactInput.namePrefix)
193
+ .withValue(StructuredName.SUFFIX, contactInput.nameSuffix);
194
+ ops.add(op.build());
195
+
196
+ // Organization
197
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
198
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
199
+ // Add to this key
200
+ .withValue(ContactsContract.Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE)
201
+ // Data
202
+ .withValue(Organization.COMPANY, contactInput.organizationName)
203
+ .withValue(Organization.TITLE, contactInput.organizationJobTitle)
204
+ .withValue(Organization.DEPARTMENT, contactInput.organizationDepartment);
205
+ ops.add(op.build());
206
+
207
+ // Birthday
208
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
209
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
210
+ // Add to this key
211
+ .withValue(ContactsContract.Data.MIMETYPE, Event.CONTENT_ITEM_TYPE)
212
+ // Data
213
+ .withValue(Event.START_DATE, contactInput.birthday)
214
+ .withValue(Event.TYPE, ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY);
215
+ ops.add(op.build());
216
+
217
+ // Note
218
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
219
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
220
+ // Add to this key
221
+ .withValue(ContactsContract.Data.MIMETYPE, Note.CONTENT_ITEM_TYPE)
222
+ // Data
223
+ .withValue(Note.NOTE, contactInput.note);
224
+ ops.add(op.build());
225
+
226
+ // @TODO not sure where to allow yields
227
+ // https://www.grokkingandroid.com/androids-contentprovideroperation-withyieldallowed-explained/
228
+ op.withYieldAllowed(true);
229
+
230
+ // Phones
231
+ for (int i = 0; i < contactInput.phones.size(); i++) {
232
+ CreateContactInput.PhoneInput phone = contactInput.phones.get(i);
233
+
234
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
235
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
236
+ // Add to this key
237
+ .withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
238
+ // Data
239
+ .withValue(Phone.TYPE, phone.type)
240
+ .withValue(Phone.LABEL, phone.label)
241
+ .withValue(Phone.NUMBER, phone.number);
242
+ if (phone.isPrimary) {
243
+ op.withValue(Phone.IS_PRIMARY, true);
244
+ }
245
+ ops.add(op.build());
246
+ }
247
+
248
+ // Emails
249
+ for (int i = 0; i < contactInput.emails.size(); i++) {
250
+ CreateContactInput.EmailInput email = contactInput.emails.get(i);
251
+
252
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
253
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
254
+ // Add to this key
255
+ .withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
256
+ // Data
257
+ .withValue(Email.TYPE, email.type)
258
+ .withValue(Email.LABEL, email.label)
259
+ .withValue(Email.ADDRESS, email.address);
260
+ if (email.isPrimary) {
261
+ op.withValue(Email.IS_PRIMARY, true);
262
+ }
263
+ ops.add(op.build());
264
+ }
265
+
266
+ // URLs
267
+ for (int i = 0; i < contactInput.urls.size(); i++) {
268
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
269
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
270
+ // Add to this key
271
+ .withValue(ContactsContract.Data.MIMETYPE, Website.CONTENT_ITEM_TYPE)
272
+ // Data
273
+ .withValue(Website.URL, contactInput.urls.get(i));
274
+ ops.add(op.build());
275
+ }
276
+
277
+ // Postal Addresses
278
+ for (int i = 0; i < contactInput.postalAddresses.size(); i++) {
279
+ CreateContactInput.PostalAddressInput address = contactInput.postalAddresses.get(i);
280
+
281
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
282
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
283
+ // Add to this key
284
+ .withValue(ContactsContract.Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)
285
+ // Data
286
+ .withValue(StructuredPostal.TYPE, address.type)
287
+ .withValue(StructuredPostal.LABEL, address.label)
288
+ .withValue(StructuredPostal.STREET, address.street)
289
+ .withValue(StructuredPostal.NEIGHBORHOOD, address.neighborhood)
290
+ .withValue(StructuredPostal.CITY, address.city)
291
+ .withValue(StructuredPostal.REGION, address.region)
292
+ .withValue(StructuredPostal.POSTCODE, address.postcode)
293
+ .withValue(StructuredPostal.COUNTRY, address.country);
294
+ if (address.isPrimary) {
295
+ op.withValue(StructuredPostal.IS_PRIMARY, true);
296
+ }
297
+ ops.add(op.build());
298
+ }
299
+
300
+ // Image
301
+ if (contactInput.image != null && contactInput.image.base64String != null) {
302
+ byte[] photoData = Base64.decode(contactInput.image.base64String, Base64.DEFAULT);
303
+
304
+ op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
305
+ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
306
+ // Add to this key
307
+ .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
308
+ // Data
309
+ .withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, photoData);
310
+ ops.add(op.build());
311
+ }
312
+
313
+ try {
314
+ ContentResolver cr = this.mActivity.getContentResolver();
315
+ ContentProviderResult[] result = cr.applyBatch(ContactsContract.AUTHORITY, ops);
316
+
317
+ if (result != null && result.length > 0 && result[0] != null) {
318
+ // This will return a URI for retrieving the contact, e.g.: "content://com.android.contacts/raw_contacts/1234"
319
+ Uri uri = result[0].uri;
320
+ // Parse the rawId from this URI.
321
+ String rawId = getIdFromUri(uri);
322
+ if (rawId != null) {
323
+ // Get the contactId from the rawId and return it.
324
+ return getContactIdByRawId(rawId);
325
+ }
326
+ }
327
+ } catch (Exception e) {
328
+ // @TODO: we might want to throw an error somehow
329
+ }
330
+
331
+ return null;
332
+ }
333
+
334
+ public boolean deleteContact(@NonNull String contactId) {
335
+ try {
336
+ // This will compose a URI, used for deleting that specific contact,
337
+ // e.g.: "content://com.android.contacts/contacts/1234"
338
+ Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, contactId);
339
+ ContentResolver cr = this.mActivity.getContentResolver();
340
+ int deleted = cr.delete(uri, null, null);
341
+ return deleted > 0;
342
+ } catch (Exception e) {
343
+ // Something went wrong
344
+ return false;
345
+ }
346
+ }
347
+
348
+ public static @Nullable String getIdFromUri(@Nullable Uri uri) {
349
+ if (uri != null) {
350
+ return uri.getLastPathSegment();
351
+ }
352
+ return null;
353
+ // If we want to support long, we can do something like this instead:
354
+ // long id = ContentUris.parseId(uri);
355
+ // if (id == -1) {
356
+ // return null;
357
+ // }
358
+ // return id;
359
+ }
360
+
361
+ private String getContactIdByRawId(@NonNull String contactRawId) {
362
+ String[] projection = new String[] { ContactsContract.RawContacts.CONTACT_ID };
363
+
364
+ String selection = ContactsContract.RawContacts._ID + "= ?";
365
+ String[] selectionArgs = new String[] { contactRawId };
366
+
367
+ ContentResolver cr = this.mActivity.getContentResolver();
368
+ Cursor cursor = cr.query(ContactsContract.RawContacts.CONTENT_URI, projection, selection, selectionArgs, null);
369
+
370
+ String contactId = null;
371
+
372
+ if (cursor.moveToNext()) {
373
+ int index = cursor.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID);
374
+
375
+ if (index >= 0) {
376
+ contactId = cursor.getString(index);
377
+ }
378
+ }
379
+
380
+ cursor.close();
381
+
382
+ return contactId;
383
+ }
384
+ }
@@ -0,0 +1,255 @@
1
+ package getcapacitor.community.contacts;
2
+
3
+ import android.Manifest;
4
+ import android.app.Activity;
5
+ import android.content.Intent;
6
+ import android.net.Uri;
7
+ import android.provider.ContactsContract;
8
+ import androidx.activity.result.ActivityResult;
9
+ import com.getcapacitor.JSArray;
10
+ import com.getcapacitor.JSObject;
11
+ import com.getcapacitor.Logger;
12
+ import com.getcapacitor.PermissionState;
13
+ import com.getcapacitor.Plugin;
14
+ import com.getcapacitor.PluginCall;
15
+ import com.getcapacitor.PluginMethod;
16
+ import com.getcapacitor.annotation.ActivityCallback;
17
+ import com.getcapacitor.annotation.CapacitorPlugin;
18
+ import com.getcapacitor.annotation.Permission;
19
+ import com.getcapacitor.annotation.PermissionCallback;
20
+ import java.util.HashMap;
21
+ import java.util.Map;
22
+ import java.util.concurrent.ExecutorService;
23
+ import java.util.concurrent.Executors;
24
+
25
+ @CapacitorPlugin(
26
+ name = "Contacts",
27
+ permissions = { @Permission(strings = { Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS }, alias = "contacts") }
28
+ )
29
+ public class ContactsPlugin extends Plugin {
30
+
31
+ public static final String TAG = "Contacts";
32
+
33
+ private Contacts implementation;
34
+
35
+ @Override
36
+ public void load() {
37
+ implementation = new Contacts(getActivity());
38
+ }
39
+
40
+ private void requestContactsPermission(PluginCall call) {
41
+ requestPermissionForAlias("contacts", call, "permissionCallback");
42
+ }
43
+
44
+ /**
45
+ * Checks the the given permission is granted or not
46
+ * @return Returns true if the permission is granted and false if it is denied.
47
+ */
48
+ private boolean isContactsPermissionGranted() {
49
+ return getPermissionState("contacts") == PermissionState.GRANTED;
50
+ }
51
+
52
+ @PermissionCallback
53
+ private void permissionCallback(PluginCall call) {
54
+ if (!isContactsPermissionGranted()) {
55
+ call.reject("Permission is required to access contacts.");
56
+ return;
57
+ }
58
+
59
+ switch (call.getMethodName()) {
60
+ case "getContact":
61
+ getContact(call);
62
+ break;
63
+ case "getContacts":
64
+ getContacts(call);
65
+ break;
66
+ case "createContact":
67
+ createContact(call);
68
+ break;
69
+ case "deleteContact":
70
+ deleteContact(call);
71
+ break;
72
+ case "pickContact":
73
+ pickContact(call);
74
+ break;
75
+ }
76
+ }
77
+
78
+ @PluginMethod
79
+ public void getContact(PluginCall call) {
80
+ try {
81
+ if (!isContactsPermissionGranted()) {
82
+ requestContactsPermission(call);
83
+ } else {
84
+ String contactId = call.getString("contactId");
85
+
86
+ if (contactId == null) {
87
+ call.reject("Parameter `contactId` not provided.");
88
+ return;
89
+ }
90
+
91
+ GetContactsProjectionInput projectionInput = new GetContactsProjectionInput(call.getObject("projection"));
92
+
93
+ ContactPayload contact = implementation.getContact(contactId, projectionInput);
94
+
95
+ if (contact == null) {
96
+ call.reject("Contact not found.");
97
+ return;
98
+ }
99
+
100
+ JSObject result = new JSObject();
101
+ result.put("contact", contact.getJSObject());
102
+ call.resolve(result);
103
+ }
104
+ } catch (Exception exception) {
105
+ rejectCall(call, exception);
106
+ }
107
+ }
108
+
109
+ @PluginMethod
110
+ public void getContacts(PluginCall call) {
111
+ try {
112
+ if (!isContactsPermissionGranted()) {
113
+ requestContactsPermission(call);
114
+ } else {
115
+ ExecutorService executor = Executors.newSingleThreadExecutor();
116
+
117
+ executor.execute(
118
+ new Runnable() {
119
+ @Override
120
+ public void run() {
121
+ try {
122
+ HashMap<String, ContactPayload> contacts = implementation.getContacts(
123
+ new GetContactsProjectionInput(call.getObject("projection"))
124
+ );
125
+
126
+ JSArray contactsJSArray = new JSArray();
127
+ for (Map.Entry<String, ContactPayload> entry : contacts.entrySet()) {
128
+ ContactPayload value = entry.getValue();
129
+ contactsJSArray.put(value.getJSObject());
130
+ }
131
+
132
+ JSObject result = new JSObject();
133
+ result.put("contacts", contactsJSArray);
134
+
135
+ bridge
136
+ .getActivity()
137
+ .runOnUiThread(
138
+ new Runnable() {
139
+ @Override
140
+ public void run() {
141
+ call.resolve(result);
142
+ }
143
+ }
144
+ );
145
+ } catch (Exception exception) {
146
+ rejectCall(call, exception);
147
+ }
148
+ }
149
+ }
150
+ );
151
+
152
+ executor.shutdown();
153
+ }
154
+ } catch (Exception exception) {
155
+ rejectCall(call, exception);
156
+ }
157
+ }
158
+
159
+ @PluginMethod
160
+ public void createContact(PluginCall call) {
161
+ try {
162
+ if (!isContactsPermissionGranted()) {
163
+ requestContactsPermission(call);
164
+ } else {
165
+ String contactId = implementation.createContact(new CreateContactInput(call.getObject("contact")));
166
+
167
+ if (contactId == null) {
168
+ call.reject("Something went wrong.");
169
+ return;
170
+ }
171
+
172
+ JSObject result = new JSObject();
173
+ result.put("contactId", contactId);
174
+
175
+ call.resolve(result);
176
+ }
177
+ } catch (Exception exception) {
178
+ rejectCall(call, exception);
179
+ }
180
+ }
181
+
182
+ @PluginMethod
183
+ public void deleteContact(PluginCall call) {
184
+ try {
185
+ if (!isContactsPermissionGranted()) {
186
+ requestContactsPermission(call);
187
+ } else {
188
+ String contactId = call.getString("contactId");
189
+
190
+ if (contactId == null) {
191
+ call.reject("Parameter `contactId` not provided.");
192
+ return;
193
+ }
194
+
195
+ if (!implementation.deleteContact(contactId)) {
196
+ call.reject("Something went wrong.");
197
+ return;
198
+ }
199
+
200
+ call.resolve();
201
+ }
202
+ } catch (Exception exception) {
203
+ rejectCall(call, exception);
204
+ }
205
+ }
206
+
207
+ @PluginMethod
208
+ public void pickContact(PluginCall call) {
209
+ try {
210
+ if (!isContactsPermissionGranted()) {
211
+ requestContactsPermission(call);
212
+ } else {
213
+ Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
214
+ startActivityForResult(call, contactPickerIntent, "pickContactResult");
215
+ }
216
+ } catch (Exception exception) {
217
+ rejectCall(call, exception);
218
+ }
219
+ }
220
+
221
+ @ActivityCallback
222
+ private void pickContactResult(PluginCall call, ActivityResult activityResult) {
223
+ if (call != null && activityResult.getResultCode() == Activity.RESULT_OK && activityResult.getData() != null) {
224
+ // This will return a URI for retrieving the contact, e.g.: "content://com.android.contacts/contacts/1234"
225
+ Uri uri = activityResult.getData().getData();
226
+ // Parse the contactId from this URI.
227
+ String contactId = Contacts.getIdFromUri(uri);
228
+
229
+ if (contactId == null) {
230
+ call.reject("Parameter `contactId` not returned from pick. Please raise an issue in GitHub if this problem persists.");
231
+ return;
232
+ }
233
+
234
+ GetContactsProjectionInput projectionInput = new GetContactsProjectionInput(call.getObject("projection"));
235
+
236
+ ContactPayload contact = implementation.getContact(contactId, projectionInput);
237
+
238
+ if (contact == null) {
239
+ call.reject("Contact not found.");
240
+ return;
241
+ }
242
+
243
+ JSObject result = new JSObject();
244
+ result.put("contact", contact.getJSObject());
245
+ call.resolve(result);
246
+ }
247
+ }
248
+
249
+ private void rejectCall(PluginCall call, Exception exception) {
250
+ String message = exception.getMessage();
251
+ message = (message != null) ? message : "An error occurred.";
252
+ Logger.error(TAG, message, exception);
253
+ call.reject(message);
254
+ }
255
+ }