Skip to content

Commit

Permalink
fix(plugin): contacts without an e-mail address are not being display…
Browse files Browse the repository at this point in the history
…ed on Android

- Modify the PICK intent so that it loads all root level contact entries (i.e. not only those with an e-mail address)
- Add two content queries so that first the root level contact (ContactsContract.Contacts), and then the subordinate contact data records (ContactsContract.Contacts.Data) are retrieved. This makes it possible to load e-mails and phone numbers for a contact, stored in the latter.
- Add the .gradle/ folder to .gitignore

Refs TeamMaestro#1
  • Loading branch information
Peter Velosy committed Jul 29, 2020
1 parent 348e658 commit 9e83d8a
Show file tree
Hide file tree
Showing 19 changed files with 343 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
dist
.gradle/
Binary file not shown.
Binary file removed android/.gradle/5.6.4/fileChanges/last-build.bin
Binary file not shown.
Binary file removed android/.gradle/5.6.4/fileHashes/fileHashes.bin
Binary file not shown.
Binary file removed android/.gradle/5.6.4/fileHashes/fileHashes.lock
Binary file not shown.
Empty file.
Binary file not shown.
2 changes: 0 additions & 2 deletions android/.gradle/buildOutputCleanup/cache.properties

This file was deleted.

Binary file removed android/.gradle/buildOutputCleanup/outputFiles.bin
Binary file not shown.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.teamhive.capacitor;

import android.database.Cursor;
import android.provider.ContactsContract;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.teamhive.capacitor.contentQuery.ContentQueryService;
import com.teamhive.capacitor.utils.Visitor;

import java.util.Map;

public class ContactDataExtractorVisitor implements Visitor<Cursor> {

private Map<String, String> projectionMap;

private JSArray phoneNumbers = new JSArray();
private JSArray emailAddresses = new JSArray();

public ContactDataExtractorVisitor(Map<String, String> projectionMap) {
this.projectionMap = projectionMap;
}

@Override
public void visit(Cursor cursor) {
JSObject currentDataRecord = ContentQueryService.extractDataFromResultSet(cursor, projectionMap);
String currentMimeType = currentDataRecord.getString(PluginContactFields.MIME_TYPE);

if (ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(currentMimeType)) {
emailAddresses.put(currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1));
} else if (ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(currentMimeType)) {
phoneNumbers.put(currentDataRecord.getString(ContactsContract.Contacts.Data.DATA1));
}
}

public JSArray getPhoneNumbers() {
return phoneNumbers;
}

public JSArray getEmailAddresses() {
return emailAddresses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.teamhive.capacitor;

import android.database.Cursor;
import com.getcapacitor.JSObject;
import com.teamhive.capacitor.contentQuery.ContentQueryService;
import com.teamhive.capacitor.utils.Visitor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ContactExtractorVisitor implements Visitor<Cursor> {

private Map<String, String> projectionMap;

private List<JSObject> contacts = new ArrayList<>();

public ContactExtractorVisitor(Map<String, String> projectionMap) {
this.projectionMap = projectionMap;
}

@Override
public void visit(Cursor cursor) {
JSObject contact = ContentQueryService.extractDataFromResultSet(cursor, projectionMap);
contacts.add(contact);
}

public List<JSObject> getContacts() {
return contacts;
}
}
135 changes: 85 additions & 50 deletions android/src/main/java/com/teamhive/capacitor/ContactPicker.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
package com.teamhive.capacitor;

import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.NativePlugin;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import com.getcapacitor.*;
import com.teamhive.capacitor.contentQuery.ContentQuery;
import com.teamhive.capacitor.contentQuery.ContentQueryService;
import com.teamhive.capacitor.utils.Utils;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@NativePlugin(
permissions={ Manifest.permission.READ_CONTACTS },
permissions = {Manifest.permission.READ_CONTACTS},
requestCodes = {
ContactPicker.REQUEST_OPEN_CODE,
ContactPicker.REQUEST_FETCH_CODE,
ContactPicker.REQUEST_PERMISSIONS_CODE
}
)
public class ContactPicker extends Plugin {

// Request codes
protected static final int REQUEST_OPEN_CODE = 11222;
protected static final int REQUEST_FETCH_CODE = 10012;
protected static final int REQUEST_PERMISSIONS_CODE = 10312;

private static final String[] CONTACT_FIELDS_PROJECTION;
private static final Map<String, String> CONTACT_FIELDS_MAP = new HashMap<String, String>();
// Messages
public static final String ERROR_READ_CONTACT = "Unable to read contact data.";
public static final String ERROR_NO_PERMISSION = "User denied permission";

static {
CONTACT_FIELDS_MAP.put(CommonDataKinds.Phone.DISPLAY_NAME, "displayName");
CONTACT_FIELDS_MAP.put(CommonDataKinds.Email.ADDRESS, "emailAddress");
CONTACT_FIELDS_PROJECTION = CONTACT_FIELDS_MAP.keySet().toArray(new String[]{});
}
// Queries
public static final String CONTACT_DATA_SELECT_CLAUSE = ContactsContract.Data.LOOKUP_KEY + " = ? AND " + ContactsContract.Data.MIMETYPE + " IN('" + CommonDataKinds.Email.CONTENT_ITEM_TYPE + "', '" + CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "')";

@PluginMethod()
public void open(PluginCall call) {
Expand All @@ -48,8 +46,7 @@ public void open(PluginCall call) {
return;
}
saveCall(call);
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK);
contactPickerIntent.setType(CommonDataKinds.Email.CONTENT_TYPE);
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(call, contactPickerIntent, REQUEST_OPEN_CODE);
}

Expand All @@ -67,9 +64,9 @@ protected void handleRequestPermissionsResult(int requestCode, String[] permissi
return;
}

for (int result: grantResults) {
for (int result : grantResults) {
if (result == PackageManager.PERMISSION_DENIED) {
savedCall.error("User denied permission");
savedCall.error(ERROR_NO_PERMISSION);
return;
}
}
Expand All @@ -89,41 +86,79 @@ protected void handleOnActivityResult(int requestCode, int resultCode, Intent in
return;
}
if (requestCode == REQUEST_OPEN_CODE) {
Cursor cursor = null;
try {
cursor = getContext().getContentResolver().query(intent.getData(), CONTACT_FIELDS_PROJECTION, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
JSObject tempContact = new JSObject();
try {
for (Map.Entry<String, String> entry : CONTACT_FIELDS_MAP.entrySet()) {
int columnIndex = cursor.getColumnIndex(entry.getKey());
tempContact.put(entry.getValue(), cursor.getString(columnIndex));
}
} catch (Exception e) {
e.printStackTrace();
}
JSObject contact = new JSObject();

JSArray emailAddresses = new JSArray();
emailAddresses.put(tempContact.getString("emailAddress"));

String displayName = tempContact.getString("displayName");
contact.put("emailAddresses", emailAddresses);
contact.put("givenName", displayName.split(" ")[0]);
contact.put("familyName", displayName.split(" ")[1]);

JSObject result = new JSObject();
result.put("value", contact);
savedCall.success(result);
}
} catch (Exception e) {
JSObject contact = readContactData(intent, savedCall);
savedCall.success(Utils.wrapIntoResult(contact));
} catch (IOException e) {
savedCall.error(ERROR_READ_CONTACT, e);
}
}
}

private JSObject readContactData(Intent intent, PluginCall savedCall) throws IOException {
final Map<String, String> projectionMap = getContactProjectionMap();
ContentQuery contactQuery = new ContentQuery.Builder()
.withUri(intent.getData())
.withProjection(projectionMap)
.build();

try (ContentQueryService.VisitableCursorWrapper contactVcw = ContentQueryService.query(getContext(), contactQuery)) {

ContactExtractorVisitor contactExtractor = new ContactExtractorVisitor(projectionMap);
contactVcw.accept(contactExtractor);
List<JSObject> contacts = contactExtractor.getContacts();

if (contacts.size() == 0) {
return null;
} else {
JSObject chosenContact = contacts.get(0);

} finally {
if (cursor != null) {
cursor.close();
Map<String, String> dataProjectionMap = getContactDataProjectionMap();
ContentQuery contactDataQuery = new ContentQuery.Builder()
.withUri(ContactsContract.Data.CONTENT_URI)
.withProjection(dataProjectionMap)
.withSelection(CONTACT_DATA_SELECT_CLAUSE)
.withSelectionArgs(new String[]{chosenContact.getString(PluginContactFields.IDENTIFIER)})
.withSortOrder(ContactsContract.Data.MIMETYPE)
.build();

try (ContentQueryService.VisitableCursorWrapper dataVcw = ContentQueryService.query(getContext(), contactDataQuery)) {

ContactDataExtractorVisitor contactDataExtractor = new ContactDataExtractorVisitor(dataProjectionMap);
dataVcw.accept(contactDataExtractor);

return transformContactObject(chosenContact, contactDataExtractor.getEmailAddresses(), contactDataExtractor.getPhoneNumbers());
}
}
}
}

private JSObject transformContactObject(JSObject tempContact, JSArray emailAddresses, JSArray phoneNumbers) {
JSObject contact = new JSObject();
contact.put(PluginContactFields.IDENTIFIER, tempContact.getString(PluginContactFields.IDENTIFIER));
String displayName = tempContact.getString(PluginContactFields.DISPLAY_NAME);
contact.put(PluginContactFields.FULL_NAME, displayName);
if (displayName != null && displayName.contains(" ")) {
contact.put(PluginContactFields.GIVEN_NAME, displayName.split(" ")[0]);
contact.put(PluginContactFields.FAMILY_NAME, displayName.split(" ")[1]);
}
contact.put(PluginContactFields.EMAIL_ADDRESSES, emailAddresses);
contact.put(PluginContactFields.PHONE_NUMBERS, phoneNumbers);
return contact;
}

private Map<String, String> getContactProjectionMap() {
Map<String, String> contactFieldsMap = new HashMap<>();
contactFieldsMap.put(ContactsContract.Contacts.LOOKUP_KEY, PluginContactFields.IDENTIFIER);
contactFieldsMap.put(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, PluginContactFields.DISPLAY_NAME);
return contactFieldsMap;
}

private Map<String, String> getContactDataProjectionMap() {
Map<String, String> contactFieldsMap = new HashMap<>();
contactFieldsMap.put(CommonDataKinds.Email.MIMETYPE, PluginContactFields.MIME_TYPE);
contactFieldsMap.put(ContactsContract.Data.DATA1, ContactsContract.Data.DATA1);
return contactFieldsMap;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.teamhive.capacitor;

public class PluginContactFields {
public static final String IDENTIFIER = "identifier";
public static final String DISPLAY_NAME = "displayName";
public static final String FULL_NAME = "fullName";
public static final String GIVEN_NAME = "givenName";
public static final String FAMILY_NAME = "familyName";
public static final String EMAIL_ADDRESSES = "emailAddresses";
public static final String PHONE_NUMBERS = "phoneNumbers";
public static final String MIME_TYPE = "mimeType";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.teamhive.capacitor.contentQuery;

import android.net.Uri;
import android.os.CancellationSignal;

import java.util.Map;

public class ContentQuery {

private Uri uri;
private Map<String, String> projection;
private String selection;
private String[] selectionArgs;
private String sortOrder;
private CancellationSignal cancellationSignal;

private ContentQuery() {
}

public Uri getUri() {
return uri;
}

public Map<String, String> getProjection() {
return projection;
}

public String getSelection() {
return selection;
}

public String[] getSelectionArgs() {
return selectionArgs;
}

public String getSortOrder() {
return sortOrder;
}

public CancellationSignal getCancellationSignal() {
return cancellationSignal;
}

public static class Builder {

private ContentQuery contentQuery = new ContentQuery();

public Builder withUri(Uri uri) {
contentQuery.uri = uri;
return this;
}

public Builder withProjection(Map<String, String> projection) {
contentQuery.projection = projection;
return this;
}

public Builder withSelection(String selection) {
contentQuery.selection = selection;
return this;
}

public Builder withSelectionArgs(String[] selectionArgs) {
contentQuery.selectionArgs = selectionArgs;
return this;
}

public Builder withSortOrder(String sortOrder) {
contentQuery.sortOrder = sortOrder;
return this;
}

public Builder withCancellationSignal(CancellationSignal cancellationSignal) {
contentQuery.cancellationSignal = cancellationSignal;
return this;
}

public ContentQuery build() {
return contentQuery;
}

}
}
Loading

0 comments on commit 9e83d8a

Please sign in to comment.