1

Anyone know how to fix this NullPointerException on the start of setup of the IabHelper?

java.lang.NullPointerException
       at android.app.ApplicationPackageManager.queryIntentServicesAsUser(ApplicationPackageManager.java:559)
       at android.app.ApplicationPackageManager.queryIntentServices(ApplicationPackageManager.java:571)
       at IabHelper.startSetup(IabHelper.java:266)
       at MyApplication.createIABHelper(MyApplication.java:256)
       at MyApplication.onCreate(MyApplication.java:156)
       at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1000)
       at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4391)
       at android.app.ActivityThread.access$1300(ActivityThread.java:141)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1294)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:137)
       at android.app.ActivityThread.main(ActivityThread.java:5041)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:511)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:816)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:582)
       at dalvik.system.NativeStart.main(NativeStart.java)

My IabHelper code is slightly modified because of other issues with it so here is the startSetup method:

public void startSetup(final OnIabSetupFinishedListener listener) {
        // If already set up, can't do it again.
        checkNotDisposed();
        if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");

        // Connection to IAB service
        logDebug("Starting in-app billing setup.");
        mServiceConn = new ServiceConnection() {
            @Override
            public void onServiceDisconnected(ComponentName name) {
                logDebug("Billing service disconnected.");
                mService = null;
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                if (mDisposed) return;
                logDebug("Billing service connected.");
                mService = IInAppBillingService.Stub.asInterface(service);
                String packageName = mContext.getPackageName();
                try {
                    logDebug("Checking for in-app billing 3 support.");

                    // check for in-app billing v3 support
                    int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
                    if (response != BILLING_RESPONSE_RESULT_OK) {
                        if (listener != null) listener.onIabSetupFinished(new IabResult(response,
                                "Error checking for billing v3 support."));

                        // if in-app purchases aren't supported, neither are subscriptions.
                        mSubscriptionsSupported = false;
                        return;
                    }
                    logDebug("In-app billing version 3 supported for " + packageName);

                    // check for v3 subscriptions support
                    response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
                    if (response == BILLING_RESPONSE_RESULT_OK) {
                        logDebug("Subscriptions AVAILABLE.");
                        mSubscriptionsSupported = true;
                    } else {
                        logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
                    }

                    mSetupDone = true;
                } catch (RemoteException e) {
                    if (listener != null) {
                        listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
                                "RemoteException while setting up in-app billing."));
                    }
                    e.printStackTrace();
                    return;
                }

                if (listener != null) {
                    listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
                }
            }
        };

        Intent serviceIntent = getExplicitIapIntent();
        PackageManager pm = mContext.getPackageManager();
        List<ResolveInfo> intentServices = pm.queryIntentServices(serviceIntent, 0);
        if (intentServices != null && !intentServices.isEmpty()) {
            //this was replaced per this comment http://stackoverflow.com/a/24202135/704836
            //if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
            // service available to handle that Intent
            mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
        } else {
            // no service available to handle that Intent
            if (listener != null) {
                listener.onIabSetupFinished(
                        new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
                                "Billing service unavailable on device."));
            }
        }
    }

The NullPointerException is happening at List<ResolveInfo> intentServices = pm.queryIntentServices(serviceIntent, 0);

Any ideas?

Thanks.

EDIT: here is the code for getExplicitIapIntent:

 /**
     * From http://stackoverflow.com/a/26318757/704836
     * @return
     */
    private Intent getExplicitIapIntent() {
        PackageManager pm = mContext.getPackageManager();
        Intent implicitIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
        implicitIntent.setPackage("com.android.vending");
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(implicitIntent, 0);

        // Is somebody else trying to intercept our IAP call?
        if (resolveInfos == null || resolveInfos.size() != 1) {
            return null;
        }

        ResolveInfo serviceInfo = resolveInfos.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);
        Intent iapIntent = new Intent();
        iapIntent.setComponent(component);
        return iapIntent;
    }

EDIT: I should also point out that according to crashlytics, 100% of the devices giving this error are rooted. So maybe it is something to do with people trying to get around having to pay for features.

EDIT: I tried passing null instead of serviceIntent and got the following exception:

java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Intent.resolveTypeIfNeeded(android.content.ContentResolver)' on a null object reference
            at android.app.ApplicationPackageManager.queryIntentServicesAsUser(ApplicationPackageManager.java:644)
            at android.app.ApplicationPackageManager.queryIntentServices(ApplicationPackageManager.java:656)
            at IabHelper.startSetup(IabHelper.java:266)

On that exception the line numbers are different from the reports I've gotten, so I'm not certain it is the same.

EDIT: I think the exception I got on the last edit might be pretty much the same as the exception I am getting for 5.0.2 devices. Here is one of the 5.0.2 reports:

 java.lang.NullPointerException
Attempt to invoke virtual method 'java.lang.String android.content.Intent.resolveTypeIfNeeded(android.content.ContentResolver)' on a null object reference

android.app.ApplicationPackageManager.queryIntentServicesAsUser (ApplicationPackageManager.java:645)

android.app.ApplicationPackageManager.queryIntentServices (ApplicationPackageManager.java:657)

IabHelper.startSetup (IabHelper.java:266)

EDIT: I went ahead and modified the code to throw an exception when serviceIntent is null and I've already had a few reports come back from my beta testers. All 100% rooted devices so I am guessing their devices don't have the correct com.android.vending.billing.InAppBillingService.BIND.

EDIT: Once the code was released the 100% rooted devices dropped to about 80%. Anyways I got a chance to troubleshoot with a user and it turned out that the getExplicitIntent method can return null sometimes under kitkat (not sure which other versions) so I went ahead and added an answer with how I changed the code.

casolorz
  • 8,486
  • 19
  • 93
  • 200
  • There is no info related to the method queryIntentServices and possible exceptions in the Android documentation. Maybe the serviceIntent is null? Try passing a null value and see if you get the same exception? – hmartinezd Jan 27 '15 at 19:22
  • I passed null and the exception is similar but not the exact same. The line numbers are different as well. On the exact same version of Android (5.0.2 on Nexus 7) the user report is like 645 and mine crashes on 644. – casolorz Jan 27 '15 at 19:51
  • After looking over all the crash reports, the 5.0.2 reports look very similar. There is a single line number difference. I am pushing out an update to check `serviceIntent` against `null` but if that is the case then there is probably nothing I can do as those users are probably just trying to circumvent the in-app purchase system. – casolorz Jan 27 '15 at 20:16
  • 1
    Yeap serviceIntent was null, I'm guessing because the devices are rooted that they have something trying to impersonate the billing services. – casolorz Jan 28 '15 at 05:21

3 Answers3

0

Do you have the code to the method queryIntentServicesAsUser()? After taking a closer look, it seems like one of the variables you are sending the queryIntentServices could be causing the NullPointer.

vdelricco
  • 749
  • 4
  • 15
  • The object `pm` itself is not null because the stack trace shows calls within it. It is something deeper in there. I have added the code for `getExplicitIapIntent` since that is also different from the sample `IabHelper`. – casolorz Jan 27 '15 at 19:03
  • Have you put a print in your getExplicitIapIntent() method where you explicitly return null to make sure that case isn't being satisfied? – vdelricco Jan 27 '15 at 19:07
  • That is a good point @vince, I'm not certain that isn't the case. I haven't actually reproduced the issue, I am only getting this on crash reports on crashlytics. – casolorz Jan 27 '15 at 19:32
  • Oh I see. According to your latest edit, it seems as though returning null doesn't create the same backtrace or exception, so that may rule out my thought that the `getExplicitIapIntent` is returning `null` – vdelricco Jan 27 '15 at 19:48
  • Yeah, it looks very similar but not exactly the same. Since this is only going out to my beta community I might just add a null check there first just to see if that is even the case. – casolorz Jan 27 '15 at 19:54
  • Just another thought..is passing `null` the same as passing an object that is set to `null`? For example, is doing `pm.queryIntentServices(null, 0);` the same as doing `Intent serviceIntent = null; pm.queryIntentServices(serviceIntent, 0);`? – vdelricco Jan 27 '15 at 19:56
  • The serviceIntent is null so it does seem to be a case that these users with rooted devices are trying to fool the app or something. – casolorz Jan 28 '15 at 05:21
  • Are you talking about the line numbers being different on the stacktrace? I think that was because they were on different versions I think. 5.0.1 gave me a different trace than 5.0.2. – casolorz Feb 26 '15 at 16:35
  • Also I know it is coming from there now because I use crashlytics and I'm trapping the exception and reporting it to crashlytics. That is how I got the 80% and 1% numbers. – casolorz Feb 26 '15 at 16:36
  • By the way I added an answer because of a new issue I found. – casolorz Mar 05 '15 at 02:54
0

So finally what is the right way to fix this problem? serviceIntent is null - that means someone wants to hack? And we let app crash in this situation?

TOP
  • 2,574
  • 5
  • 35
  • 60
  • That is my guess. I am catching it and showing an error instead of crashing because it is a small amount of users and don't really care, don't think they are getting the premium features. I am not 100% sure they are all trying to work around in-app purchases because not all of them are rooted (in my case), only like 80% are. – casolorz Feb 26 '15 at 12:50
  • @mntgoat Does method getExplicitIapIntent() work well? I 'm facing problem "Service Intent must be explicit" and I read your answer at topic about it. I 'm using original code of IabHelper from sample of Android. – TOP Feb 26 '15 at 15:42
  • It works but 1% of my users seem to get a null. 80% of those nulls are rooted devices. I don't know how much better that is compared to the `.setPackage("com.android.vending");` solution others claim works as I didn't have this kind of stats back when I used that. – casolorz Feb 26 '15 at 16:22
  • Thanks a lot. And I want to know what did you do to protect your app from hacker? I mean users, who want to use pro-features without paying any money? – TOP Feb 26 '15 at 16:31
  • I added an answer because of a new issue I found. – casolorz Mar 05 '15 at 02:55
  • someone trying a more complex hack could have replaced the classes entirely on a rooted phone - and could have edited your check too. check serverside if things were actually paid for before granting access to any resources that need to be paid for. – Lassi Kinnunen Aug 10 '16 at 04:15
0

I finally found a user getting a null intent who was not doing anything dodgy with in-app purchases. He was on kitkat. The fix was this:

if (OSUtils.lollipopOrHigher) {
            serviceIntent = getExplicitIapIntent();
        } else {
            serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
            serviceIntent.setPackage("com.android.vending");
        }

Another option would be to check for null and then try the non lollipop method.

casolorz
  • 8,486
  • 19
  • 93
  • 200