简体   繁体   中英

cordova-plugin-contacts - On contact pick app crash on android M

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.

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

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM