Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(plugin): contacts without an e-mail address are not being displayed on Android #2

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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