Reputation: 31
I an using cordova-plugin-contacts to pick a contact from contacts. App is working fine on Android 5(Lollipop) and prior versions. But on Android 6(Marshmallow) app crashes when I pick a contact.
Here is my javascript:
navigator.contacts.pickContact(function(contact){
$scope.contact.name=contact.displayName;
if(contact.phoneNumbers) {
msgToastService.toastMsgAlert('Number picked: ' + contact.phoneNumbers,'Contact',"SC");
} else {
msgToastService.toastMsgAlert('Choose Valid Mobile Number!','Contact',"SC");
}
$scope.$apply();
},function(err){
});
ContactManager.java
public class ContactManager extends CordovaPlugin {
private ContactAccessor contactAccessor;
private CallbackContext callbackContext; // The callback context from which we were invoked.
private JSONArray executeArgs;
private static final String LOG_TAG = "Contact Query";
public static final int UNKNOWN_ERROR = 0;
public static final int INVALID_ARGUMENT_ERROR = 1;
public static final int TIMEOUT_ERROR = 2;
public static final int PENDING_OPERATION_ERROR = 3;
public static final int IO_ERROR = 4;
public static final int NOT_SUPPORTED_ERROR = 5;
public static final int PERMISSION_DENIED_ERROR = 20;
private static final int CONTACT_PICKER_RESULT = 1000;
public static String [] permissions;
//Request code for the permissions picker (Pick is async and uses intents)
public static final int SEARCH_REQ_CODE = 0;
public static final int SAVE_REQ_CODE = 1;
public static final int REMOVE_REQ_CODE = 2;
public static final int PICK_REQ_CODE = 3;
public static final String READ = Manifest.permission.READ_CONTACTS;
public static final String WRITE = Manifest.permission.WRITE_CONTACTS;
public static int instanceCounter = 0;
/**
* Constructor.
*/
public ContactManager() {
instanceCounter++;
Log.e(this.getClass().getName(), "Instance created: " + instanceCounter);
}
protected void getReadPermission(int requestCode)
{
cordova.requestPermission(this, requestCode, READ);
}
protected void getWritePermission(int requestCode)
{
cordova.requestPermission(this, requestCode, WRITE);
}
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArray of arguments for the plugin.
* @param callbackContext The callback context used when calling back into JavaScript.
* @return True if the action was valid, false otherwise.
*/
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
this.callbackContext = callbackContext;
this.executeArgs = args;
/**
* Check to see if we are on an Android 1.X device. If we are return an error as we
* do not support this as of Cordova 1.0.
*/
if (android.os.Build.VERSION.RELEASE.startsWith("1.")) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ContactManager.NOT_SUPPORTED_ERROR));
return true;
}
/**
* Only create the contactAccessor after we check the Android version or the program will crash
* older phones.
*/
if (this.contactAccessor == null) {
this.contactAccessor = new ContactAccessorSdk5(this.cordova);
}
if (action.equals("search")) {
if(cordova.hasPermission(READ)) {
search(executeArgs);
}
else
{
getReadPermission(SEARCH_REQ_CODE);
}
}
else if (action.equals("save")) {
if(cordova.hasPermission(WRITE))
{
save(executeArgs);
}
else
{
getWritePermission(SAVE_REQ_CODE);
}
}
else if (action.equals("remove")) {
if(cordova.hasPermission(WRITE))
{
remove(executeArgs);
}
else
{
getWritePermission(REMOVE_REQ_CODE);
}
}
else if (action.equals("pickContact")) {
if(cordova.hasPermission(READ)) {
pickContactAsync();
PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
this.callbackContext.sendPluginResult(r);
}
else
{
getReadPermission(PICK_REQ_CODE);
}
}
else {
return false;
}
return true;
}
private void remove(JSONArray args) throws JSONException {
final String contactId = args.getString(0);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
if (contactAccessor.remove(contactId)) {
callbackContext.success();
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
});
}
private void save(JSONArray args) throws JSONException {
final JSONObject contact = args.getJSONObject(0);
this.cordova.getThreadPool().execute(new Runnable(){
public void run() {
JSONObject res = null;
String id = contactAccessor.save(contact);
if (id != null) {
try {
res = contactAccessor.getContactById(id);
} catch (JSONException e) {
Log.e(LOG_TAG, "JSON fail.", e);
}
}
if (res != null) {
callbackContext.success(res);
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
});
}
private void search(JSONArray args) throws JSONException
{
final JSONArray filter = args.getJSONArray(0);
final JSONObject options = args.get(1) == null ? null : args.getJSONObject(1);
this.cordova.getThreadPool().execute(new Runnable() {
public void run() {
JSONArray res = contactAccessor.search(filter, options);
callbackContext.success(res);
}
});
}
/**
* Launches the Contact Picker to select a single contact.
*/
private void pickContactAsync() {
final CordovaPlugin plugin = (CordovaPlugin) this;
plugin.cordova.setActivityResultCallback(this);
Runnable worker = new Runnable() {
public void run() {
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
plugin.cordova.startActivityForResult(plugin, contactPickerIntent, CONTACT_PICKER_RESULT);
}
};
this.cordova.getActivity().runOnUiThread(worker);
}
/**
* Called when user picks contact.
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
* @throws JSONException
*/
public void onActivityResult(int requestCode, int resultCode, final Intent intent) {
if (requestCode == CONTACT_PICKER_RESULT) {
if (resultCode == Activity.RESULT_OK) {
String contactId = intent.getData().getLastPathSegment();
// to populate contact data we require Raw Contact ID
// so we do look up for contact raw id first
Cursor c = this.cordova.getActivity().getContentResolver().query(RawContacts.CONTENT_URI,
new String[] {RawContacts._ID}, RawContacts.CONTACT_ID + " = " + contactId, null, null);
if (!c.moveToFirst()) {
this.callbackContext.error("Error occured while retrieving contact raw id");
return;
}
String id = c.getString(c.getColumnIndex(RawContacts._ID));
c.close();
try {
JSONObject contact = contactAccessor.getContactById(id);//Exception in this line
this.callbackContext.success(contact);
return;
} catch (JSONException e) {
Log.e(LOG_TAG, "JSON fail.", e);
}
} else if (resultCode == Activity.RESULT_CANCELED){
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.NO_RESULT, UNKNOWN_ERROR));
return;
}
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, UNKNOWN_ERROR));
}
}
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException
{
for(int r:grantResults)
{
if(r == PackageManager.PERMISSION_DENIED)
{
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return;
}
}
switch(requestCode)
{
case SEARCH_REQ_CODE:
search(executeArgs);
break;
case SAVE_REQ_CODE:
save(executeArgs);
break;
case REMOVE_REQ_CODE:
remove(executeArgs);
break;
}
}
}
Here is an exception shown in android logcat:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.paymepaisa.in, PID: 28620
java.lang.RuntimeException: Unable to resume activity {com.paymepaisa.in/com.paymepaisa.in.MainActivity}: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1000, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/1483r1353-2951434F593D2941394339.3789r1354-2951434F593D2941394339/1504191 flg=0x1 }} to activity {com.paymepaisa.in/com.paymepaisa.in.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'org.json.JSONObject org.apache.cordova.contacts.ContactAccessor.getContactById(java.lang.String)' on a null object reference
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3103)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1000, result=-1, data=Intent { dat=content://com.android.contacts/contacts/lookup/1483r1353-2951434F593D2941394339.3789r1354-2951434F593D2941394339/1504191 flg=0x1 }} to activity {com.paymepaisa.in/com.paymepaisa.in.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'org.json.JSONObject org.apache.cordova.contacts.ContactAccessor.getContactById(java.lang.String)' on a null object reference
at android.app.ActivityThread.deliverResults(ActivityThread.java:3699)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3089)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'org.json.JSONObject org.apache.cordova.contacts.ContactAccessor.getContactById(java.lang.String)' on a null object reference
at org.apache.cordova.contacts.ContactManager.onActivityResult(ContactManager.java:246)
at org.apache.cordova.CordovaInterfaceImpl.onActivityResult(CordovaInterfaceImpl.java:126)
at org.apache.cordova.CordovaActivity.onActivityResult(CordovaActivity.java:348)
at android.app.Activity.dispatchActivityResult(Activity.java:6428)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3695)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3089)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3134)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2481)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
As the exception says, there is a NullPointerException in ContactManager.java:246. So I have checked the plugin code and found that the object of ContactManager is re-created and onActivityResult is called on that. There for all member variables of ContactManager becomes null.
I can't find out why is this happening. Please look at the cordova-plugin-contacts git repository for plugin code.
My Cordova version: 5.3.1
Android SDK: Marshmallow(6) API Level-23
Please let me know if you want any more information. Thanks in advance.
Upvotes: 3
Views: 1732
Reputation: 610
Try this code
cordova.plugins.diagnostic.requestRuntimePermission(function(status){
switch(status){
case cordova.plugins.diagnostic.permissionStatus.GRANTED:
console.log("Permission granted to use the camera");
$scope.callChargeDetailLoad();
break;
case cordova.plugins.diagnostic.permissionStatus.NOT_REQUESTED:
console.log("Permission to use the camera has not been requested yet");
break;
case cordova.plugins.diagnostic.permissionStatus.DENIED:
console.log("Permission denied to use the camera - ask again?");
break;
case cordova.plugins.diagnostic.permissionStatus.DENIED_ALWAYS:
console.log("Permission permanently denied to use the camera - guess we won't be using it then!");
break;
}
}, function(error){
console.error("The following error occurred: "+error);
}, cordova.plugins.diagnostic.permission.READ_CONTACTS);
Upvotes: 0
Reputation: 79
Thats exactly because of the changes in the marshmallow for runtime permission. @Murtaza Khursheed Hussain is right
Here is how to fix: Before accessing any module of your mobile you have to make sure user allowed that permission. In your case it's contacts, this also applies to other cases like location, file storage, camera, sensors etc.
You can ask for runtime permissions using cordova.plugins.diagnostic
plugin
Install Plugin (cordova.plugins.diagnostic):
$ cordova plugin add cordova.plugins.diagnostic
Try wrapping your code as follows:
cordova.plugins.diagnostic.getPermissionAuthorizationStatus(function(status){
//Check for contact permission status
if(cordova.plugins.diagnostic.runtimePermissionStatus.GRANTED !== status){
//request for permission at runtime
cordova.plugins.diagnostic.requestRuntimePermission(function(statusAfterRequest){
if(cordova.plugins.diagnostic.runtimePermissionStatus.GRANTED === statusAfterRequest){
//Your code here..
//navigator.contacts.pickContact(....
}
}, function(error){
console.error("error while requesting permission: "+error);
},);
}
}, function(error){
console.error("The following error occurred: "+error);
}, cordova.plugins.diagnostic.runtimePermission.READ_CONTACTS);
Here is the plugin page for more information. cordova.plugins.diagnostic
Hope it helps! :) Have a great day
Upvotes: 1