Skip to content

Commit 07ae865

Browse files
authored
Merge pull request #106 from lukasgit/staging
Sync staging 0.4.5 with master
2 parents e8280ae + 4e53311 commit 07ae865

File tree

11 files changed

+785
-570
lines changed

11 files changed

+785
-570
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## [0.4.5] - April 26, 2020
2+
3+
* Fixed crashing where activity result coming back from another plugin and not handled (@lidongze91)
4+
* Fixed swift syntax error in UIActivityIndicatorView.init (@sperochon)
5+
* Added new functionality openDeviceContactPicker (@sperochon)
6+
* Function opens native device contact picker corresponding on each native platform (Android or iOS); user can then search and select a specific contact.
7+
* Android: Intent.ACTION_PICK
8+
* iOS: CNContactPickerViewController
9+
110
## [0.4.4] - April 23, 2020
211

312
* Fixed swift function name (@lidongze91)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ To use this plugin, add `contacts_service` as a [dependency in your `pubspec.yam
1212
For example:
1313
```yaml
1414
dependencies:
15-
contacts_service: ^0.4.4
15+
contacts_service: ^0.4.5
1616
```
1717
1818
## Permissions

android/src/main/java/flutter/plugins/contactsservice/contactsservice/ContactsServicePlugin.java

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
package flutter.plugins.contactsservice.contactsservice;
22

3-
import static android.provider.ContactsContract.CommonDataKinds;
4-
import static android.provider.ContactsContract.CommonDataKinds.Email;
5-
import static android.provider.ContactsContract.CommonDataKinds.Organization;
6-
import static android.provider.ContactsContract.CommonDataKinds.Phone;
7-
import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
8-
import static android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
9-
103
import android.annotation.TargetApi;
114
import android.content.ContentProviderOperation;
125
import android.content.ContentResolver;
@@ -21,23 +14,14 @@
2114
import android.os.Build;
2215
import android.provider.BaseColumns;
2316
import android.provider.ContactsContract;
24-
import android.provider.ContactsContract.RawContacts;
2517
import android.text.TextUtils;
2618
import android.util.Log;
27-
import io.flutter.embedding.engine.plugins.FlutterPlugin;
28-
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
29-
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
30-
import io.flutter.plugin.common.BinaryMessenger;
31-
import io.flutter.plugin.common.MethodCall;
32-
import io.flutter.plugin.common.MethodChannel;
33-
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
34-
import io.flutter.plugin.common.MethodChannel.Result;
35-
import io.flutter.plugin.common.PluginRegistry;
36-
import io.flutter.plugin.common.PluginRegistry.Registrar;
19+
3720
import java.io.ByteArrayOutputStream;
3821
import java.io.IOException;
3922
import java.io.InputStream;
4023
import java.util.ArrayList;
24+
import java.util.Arrays;
4125
import java.util.Collections;
4226
import java.util.Comparator;
4327
import java.util.HashMap;
@@ -47,6 +31,25 @@
4731
import java.util.concurrent.ThreadPoolExecutor;
4832
import java.util.concurrent.TimeUnit;
4933

34+
import io.flutter.embedding.engine.plugins.FlutterPlugin;
35+
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
36+
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
37+
import io.flutter.plugin.common.BinaryMessenger;
38+
import io.flutter.plugin.common.MethodCall;
39+
import io.flutter.plugin.common.MethodChannel;
40+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
41+
import io.flutter.plugin.common.MethodChannel.Result;
42+
import io.flutter.plugin.common.PluginRegistry;
43+
import io.flutter.plugin.common.PluginRegistry.Registrar;
44+
45+
import static android.app.Activity.RESULT_CANCELED;
46+
import static android.provider.ContactsContract.CommonDataKinds;
47+
import static android.provider.ContactsContract.CommonDataKinds.Email;
48+
import static android.provider.ContactsContract.CommonDataKinds.Organization;
49+
import static android.provider.ContactsContract.CommonDataKinds.Phone;
50+
import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
51+
import static android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
52+
5053
@TargetApi(Build.VERSION_CODES.ECLAIR)
5154
public class ContactsServicePlugin implements MethodCallHandler, FlutterPlugin, ActivityAware {
5255

@@ -95,10 +98,10 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) {
9598
public void onMethodCall(MethodCall call, Result result) {
9699
switch(call.method){
97100
case "getContacts": {
98-
this.getContacts((String)call.argument("query"), (boolean)call.argument("withThumbnails"), (boolean)call.argument("photoHighResolution"), (boolean)call.argument("orderByGivenName"), result);
101+
this.getContacts(call.method, (String)call.argument("query"), (boolean)call.argument("withThumbnails"), (boolean)call.argument("photoHighResolution"), (boolean)call.argument("orderByGivenName"), result);
99102
break;
100103
} case "getContactsForPhone": {
101-
this.getContactsForPhone((String)call.argument("phone"), (boolean)call.argument("withThumbnails"), (boolean)call.argument("photoHighResolution"), (boolean)call.argument("orderByGivenName"), result);
104+
this.getContactsForPhone(call.method, (String)call.argument("phone"), (boolean)call.argument("withThumbnails"), (boolean)call.argument("photoHighResolution"), (boolean)call.argument("orderByGivenName"), result);
102105
break;
103106
} case "getAvatar": {
104107
final Contact contact = Contact.fromMap((HashMap)call.argument("contact"));
@@ -145,6 +148,9 @@ public void onMethodCall(MethodCall call, Result result) {
145148
result.success(FORM_COULD_NOT_BE_OPEN);
146149
}
147150
break;
151+
} case "openDeviceContactPicker": {
152+
openDeviceContactPicker(result);
153+
break;
148154
} default: {
149155
result.notImplemented();
150156
break;
@@ -189,12 +195,12 @@ public void onMethodCall(MethodCall call, Result result) {
189195

190196

191197
@TargetApi(Build.VERSION_CODES.ECLAIR)
192-
private void getContacts(String query, boolean withThumbnails, boolean photoHighResolution, boolean orderByGivenName, Result result) {
193-
new GetContactsTask(result, withThumbnails, photoHighResolution, orderByGivenName).executeOnExecutor(executor, query, false);
198+
private void getContacts(String callMethod, String query, boolean withThumbnails, boolean photoHighResolution, boolean orderByGivenName, Result result) {
199+
new GetContactsTask(callMethod, result, withThumbnails, photoHighResolution, orderByGivenName).executeOnExecutor(executor, query, false);
194200
}
195201

196-
private void getContactsForPhone(String phone, boolean withThumbnails, boolean photoHighResolution, boolean orderByGivenName, Result result) {
197-
new GetContactsTask(result, withThumbnails, photoHighResolution, orderByGivenName).executeOnExecutor(executor, phone, true);
202+
private void getContactsForPhone(String callMethod, String phone, boolean withThumbnails, boolean photoHighResolution, boolean orderByGivenName, Result result) {
203+
new GetContactsTask(callMethod, result, withThumbnails, photoHighResolution, orderByGivenName).executeOnExecutor(executor, phone, true);
198204
}
199205

200206
@Override
@@ -228,14 +234,15 @@ public void onDetachedFromActivity() {
228234
private class BaseContactsServiceDelegate implements PluginRegistry.ActivityResultListener {
229235
private static final int REQUEST_OPEN_CONTACT_FORM = 52941;
230236
private static final int REQUEST_OPEN_EXISTING_CONTACT = 52942;
237+
private static final int REQUEST_OPEN_CONTACT_PICKER = 52943;
231238
private Result result;
232239

233240
void setResult(Result result) {
234241
this.result = result;
235242
}
236243

237244
void finishWithResult(Object result) {
238-
if(result != null) {
245+
if(this.result != null) {
239246
this.result.success(result);
240247
this.result = null;
241248
}
@@ -251,9 +258,27 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent intent)
251258
finishWithResult(FORM_OPERATION_CANCELED);
252259
}
253260
return true;
254-
} else {
255-
finishWithResult(FORM_COULD_NOT_BE_OPEN);
256261
}
262+
263+
if (requestCode == REQUEST_OPEN_CONTACT_PICKER) {
264+
if (resultCode == RESULT_CANCELED) {
265+
finishWithResult(FORM_OPERATION_CANCELED);
266+
return true;
267+
}
268+
Uri contactUri = intent.getData();
269+
Cursor cursor = contentResolver.query(contactUri, null, null, null, null);
270+
if (cursor.moveToFirst()) {
271+
String id = contactUri.getLastPathSegment();
272+
getContacts("openDeviceContactPicker", id, false, false, false, this.result);
273+
} else {
274+
Log.e(LOG_TAG, "onActivityResult - cursor.moveToFirst() returns false");
275+
finishWithResult(FORM_OPERATION_CANCELED);
276+
}
277+
cursor.close();
278+
return true;
279+
}
280+
281+
finishWithResult(FORM_COULD_NOT_BE_OPEN);
257282
return false;
258283
}
259284

@@ -284,6 +309,12 @@ void openContactForm() {
284309
}
285310
}
286311

312+
void openContactPicker() {
313+
Intent intent = new Intent(Intent.ACTION_PICK);
314+
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
315+
startIntent(intent, REQUEST_OPEN_CONTACT_PICKER);
316+
}
317+
287318
void startIntent(Intent intent, int request) {
288319
}
289320

@@ -310,7 +341,16 @@ HashMap getContactByIdentifier(String identifier) {
310341
return null;
311342
}
312343
}
313-
344+
345+
private void openDeviceContactPicker(Result result) {
346+
if (delegate != null) {
347+
delegate.setResult(result);
348+
delegate.openContactPicker();
349+
} else {
350+
result.success(FORM_COULD_NOT_BE_OPEN);
351+
}
352+
}
353+
314354
private class ContactServiceDelegateOld extends BaseContactsServiceDelegate {
315355
private final PluginRegistry.Registrar registrar;
316356

@@ -350,7 +390,11 @@ void unbindActivity() {
350390
@Override
351391
void startIntent(Intent intent, int request) {
352392
if (this.activityPluginBinding != null) {
353-
activityPluginBinding.getActivity().startActivityForResult(intent, request);
393+
if (intent.resolveActivity(context.getPackageManager()) != null) {
394+
activityPluginBinding.getActivity().startActivityForResult(intent, request);
395+
} else {
396+
finishWithResult(FORM_COULD_NOT_BE_OPEN);
397+
}
354398
} else {
355399
context.startActivity(intent);
356400
}
@@ -360,12 +404,14 @@ void startIntent(Intent intent, int request) {
360404
@TargetApi(Build.VERSION_CODES.CUPCAKE)
361405
private class GetContactsTask extends AsyncTask<Object, Void, ArrayList<HashMap>> {
362406

407+
private String callMethod;
363408
private Result getContactResult;
364409
private boolean withThumbnails;
365410
private boolean photoHighResolution;
366411
private boolean orderByGivenName;
367412

368-
public GetContactsTask(Result result, boolean withThumbnails, boolean photoHighResolution, boolean orderByGivenName){
413+
public GetContactsTask(String callMethod, Result result, boolean withThumbnails, boolean photoHighResolution, boolean orderByGivenName){
414+
this.callMethod = callMethod;
369415
this.getContactResult = result;
370416
this.withThumbnails = withThumbnails;
371417
this.photoHighResolution = photoHighResolution;
@@ -375,10 +421,12 @@ public GetContactsTask(Result result, boolean withThumbnails, boolean photoHighR
375421
@TargetApi(Build.VERSION_CODES.ECLAIR)
376422
protected ArrayList<HashMap> doInBackground(Object... params) {
377423
ArrayList<Contact> contacts;
378-
if ((Boolean) params[1])
379-
contacts = getContactsFrom(getCursorForPhone(((String) params[0])));
380-
else
381-
contacts = getContactsFrom(getCursor(((String) params[0])));
424+
switch (callMethod) {
425+
case "openDeviceContactPicker": contacts = getContactsFrom(getCursor(null, (String) params[0])); break;
426+
case "getContacts": contacts = getContactsFrom(getCursor((String) params[0], null)); break;
427+
case "getContactsForPhone": contacts = getContactsFrom(getCursorForPhone(((String) params[0]))); break;
428+
default: return null;
429+
}
382430

383431
if (withThumbnails) {
384432
for(Contact c : contacts){
@@ -418,26 +466,33 @@ public int compare(Contact contactA, Contact contactB) {
418466
}
419467

420468
protected void onPostExecute(ArrayList<HashMap> result) {
421-
getContactResult.success(result);
469+
if (result == null) {
470+
getContactResult.notImplemented();
471+
} else {
472+
getContactResult.success(result);
473+
}
422474
}
423475
}
424476

425477

426-
private Cursor getCursor(String query) {
427-
String selection = ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=? OR "
478+
private Cursor getCursor(String query, String rawContactId) {
479+
String selection = "(" + ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=? OR "
428480
+ ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=? OR "
429481
+ ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=? OR "
430-
+ ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?";
431-
String[] selectionArgs = new String[] { CommonDataKinds.Note.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE,
482+
+ ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.RawContacts.ACCOUNT_TYPE + "=?" + ")";
483+
ArrayList<String> selectionArgs = new ArrayList<>(Arrays.asList(CommonDataKinds.Note.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE,
432484
Phone.CONTENT_ITEM_TYPE, StructuredName.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
433-
StructuredPostal.CONTENT_ITEM_TYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE, ContactsContract.RawContacts.ACCOUNT_TYPE
434-
};
435-
if(query != null){
436-
selectionArgs = new String[]{query + "%"};
485+
StructuredPostal.CONTENT_ITEM_TYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE, ContactsContract.RawContacts.ACCOUNT_TYPE));
486+
if (query != null) {
487+
selectionArgs = new ArrayList<>();
488+
selectionArgs.add(query + "%");
437489
selection = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?";
438490
}
439-
440-
return contentResolver.query(ContactsContract.Data.CONTENT_URI, PROJECTION, selection, selectionArgs, null);
491+
if (rawContactId != null) {
492+
selectionArgs.add(rawContactId);
493+
selection += " AND " + ContactsContract.Data.CONTACT_ID + " =?";
494+
}
495+
return contentResolver.query(ContactsContract.Data.CONTENT_URI, PROJECTION, selection, selectionArgs.toArray(new String[selectionArgs.size()]), null);
441496
}
442497

443498
private Cursor getCursorForPhone(String phone) {
@@ -830,6 +885,9 @@ private boolean updateContact(Contact contact) {
830885
contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
831886
return true;
832887
} catch (Exception e) {
888+
// Log exception
889+
Log.e("TAG", "Exception encountered while inserting contact: " );
890+
e.printStackTrace();
833891
return false;
834892
}
835893
}

example/ios/Runner/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<key>UILaunchStoryboardName</key>
2626
<string>LaunchScreen</string>
2727
<key>NSContactsUsageDescription</key>
28-
<string>This app requires contacts access to function properly.</string>
28+
<string>This app requires contacts access to function properly.</string>
2929
<key>UIMainStoryboardFile</key>
3030
<string>Main</string>
3131
<key>UIRequiredDeviceCapabilities</key>

0 commit comments

Comments
 (0)