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.
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.
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?
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.
Related
In my app I need to monitorize recently added or updated packages, but since Oreo this is a hard task.
To do it I have a service that runs every X time to detect the new installed/updated apps.
The main core of this service is to call the getChangedPackages function from the PackageManager, but this function always returns null, even if I install or update any app from or not from the Play Store in the interval between two consequtive calls to getChangedPackages.
https://developer.android.com/reference/android/content/pm/PackageManager.html#getChangedPackages(int)
I need to request any permission to call this function? Is the getChangedPackages buggy?
private void _doProcess()
{
try
{
PackageManager package_manager = getPackageManager();
int sequence_number = ApplicationPreferences.getInteger(this, GET_CHANGED_PACKAGES_SEQUENCE_NUMBER_KEY, 0);
ChangedPackages changed_packages = package_manager.getChangedPackages(sequence_number);
LogUtilities.show(this, String.format("Retrieve recently apps installs/updates using sequence number %d returns %s", sequence_number, changed_packages == null ? "null" : "a not null object"));
if (changed_packages == null) changed_packages = package_manager.getChangedPackages(0);
LogUtilities.show(this, String.format("Retrieve recently apps installs/updates using sequence number %d returns %s", sequence_number, changed_packages == null ? "null" : "a not null object"));
if (changed_packages != null)
{
List<String> packages_names = changed_packages.getPackageNames();
LogUtilities.show(this, String.format("%d recently installed/updated apps", packages_names == null ? 0 : packages_names.size()));
if (packages_names != null) for (String package_name : packages_names) PackagesUpdatedReceiver.doProcessPackageUpdate(this, new Intent(isNewInstall(package_manager, package_name) ? Intent.ACTION_PACKAGE_ADDED : Intent.ACTION_PACKAGE_REPLACED).setData(Uri.parse(String.format("package:%s", package_name))));
LogUtilities.show(this, String.format("Storing %s is the sequence number for next iteration", changed_packages.getSequenceNumber()));
ApplicationPreferences.putInteger(this, GET_CHANGED_PACKAGES_SEQUENCE_NUMBER_KEY, changed_packages.getSequenceNumber());
}
else
{
LogUtilities.show(this, String.format("Storing %s is the sequence number for next iteration", sequence_number + 1));
ApplicationPreferences.putInteger(this, GET_CHANGED_PACKAGES_SEQUENCE_NUMBER_KEY, sequence_number + 1);
}
}
catch (Exception e)
{
LogUtilities.show(this, e);
}
}
My experimental results so far have shown that this PackageManager API method getChangedPackages() is not reliable: quite often the returned ChangedPackages value contains many unchanged packages. So I’ve decided to implement a similar feature in a class called PackageUtils, as shown below. The idea is to poll for all the installed packages, as shown in method getInstalledPackageNames() below, and compare the string list with a previously saved one. This comparison boils down to comparing 2 string lists, as shown in method operate2StringLists() below. To get a set of removed packages, use GET_1_MINUS_2_OR_REMOVED as operation. To get a set of added packages, use GET_2_MINUS_1_OR_ADDED as operation.
public class PackageUtils {
public static final int GET_1_MINUS_2_OR_REMOVED = 0;
public static final int GET_2_MINUS_1_OR_ADDED = 1;
// Get all the installed package names
public static List<String> getInstalledPackageNames(Context context) {
List<String> installedPackageNames = new ArrayList<>();
try {
PackageManager packageManager = context.getPackageManager();
List<ApplicationInfo> appInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo appInfo : appInfoList) {
installedPackageNames.add(appInfo.packageName);
}
} catch (Exception e) {
e.printStackTrace();
}
return installedPackageNames;
}
// Compare 2 string lists and return differences.
public static Set<String> operate2StringLists(List<String> pkgList1, List<String> pkgList2, int operation) {
Set<String> result = null;
Set<String> pkgSet1 = new HashSet<String>(pkgList1);
Set<String> pkgSet2 = new HashSet<String>(pkgList2);
switch (operation) {
case GET_1_MINUS_2_OR_REMOVED:
pkgSet1.removeAll(pkgSet2);
result = pkgSet1;
break;
case GET_2_MINUS_1_OR_ADDED:
pkgSet2.removeAll(pkgSet1);
result = pkgSet2;
break;
default:
break;
}
return result;
}
}
The code has been tested on an Android Oreo device. It can reliably detect all added and removed packages between 2 time instances. However, it can’t detect updated packages in-between.
Finally got it. You have to create a variable called sequenceNumber, and update it every time you query changed packages.
private static int sequenceNumber = 0;
...
PackageManager pm = getContext().getPackageManager();
ChangedPackages changedPackages = pm.getChangedPackages(sequenceNumber);
if(changedPackages != null)
sequenceNumber = changedPackages.getSequenceNumber();
New to Android dev and I have this very annoying, and familiar problem.
Short version: how can I test my app on multiple physical devices w/o buying them? Specifically, I'm trying to test on 4.1 and 4.2 running devices. I can't use an emulator because this involves in-app billing.
Long version:
Today, I got a crash report from a user (android version = 4.2, on device = A1-811 (mango)).
The issue is: IAB helper is not set up. Can't perform operation: queryInventory . I tested the app on 4.3, 5.0, 5.1 and it's fine.
I KNOW that the actual crash happened because I wasn't quitting when I was checking for (!result.isSuccess()) in mhelper.startSetup().
My question is, how do I solve the underlying issue of IabHelper not being set up?! I don't have access to Android 4.2 device...
MY CODE:
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener () {
public void onIabPurchaseFinished (IabResult result, Purchase purchase) {
if (mHelper == null)
return;
if (result.isFailure ()) {
Log.d (PURCHASE_TAG, "Google's response is a failure.");
} else {
Log.d (PURCHASE_TAG, "Response is successful. purchase.getSKU = " + purchase.getSku ());
premium = true;
}
}
};
IabHelper.QueryInventoryFinishedListener mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener () {
public void onQueryInventoryFinished (IabResult result, Inventory inventory) {
Log.d (PURCHASE_TAG, "Processing Google's response.");
// Check if user has existing purchase.
if (result.isFailure ()) {
Log.d (PURCHASE_TAG, "Google's response is a failure. Response = ");
} else {
Log.d (PURCHASE_TAG, "Google's response is success! getPurchase() = " + inventory.getPurchase (THE_SKU));
if (inventory.getPurchase (THE_SKU) == null) {
premium = false;
} else {
premium = inventory.getPurchase (THE_SKU).getSku ().equals (THE_SKU);
}
}
// Show price if user is not premium, thank you note if the user is premium
if (premium == true) {
Log.d (PURCHASE_TAG, "3 premium = " + premium);
priceView.setText ("Thank you for the purchase!");
} else {
if (inventory != null) {
String pro_price = inventory.getSkuDetails (THE_SKU).getPrice ();
priceView.setText ("" + pro_price);
}
}
}
};
private void startPurchaseQuery () {
String base64EncodedPublicKey = "the key is generated ...";
mHelper = new IabHelper (this, base64EncodedPublicKey);
Log.d (PURCHASE_TAG, "Purchase query started.");
mHelper.startSetup (new IabHelper.OnIabSetupFinishedListener () {
public void onIabSetupFinished (IabResult result) {
Log.d (PURCHASE_TAG, "IabHelper Setup successful. Querying inventory.");
if (!result.isSuccess ()) {
Log.d (PURCHASE_TAG, "Error with IabHelper setup.");
return;
}
if (mHelper == null) return;
// IAB is fully set up. Now, let's get an inventory of stuff we own.
// Build the SKU list
List<String> additionalSkuList;
additionalSkuList = new ArrayList<String> ();
additionalSkuList.add (THE_SKU);
// Make the query
mHelper.queryInventoryAsync (true, additionalSkuList, mQueryFinishedListener);
Log.d (PURCHASE_TAG, "Query finished. Premium status = " + premium);
}
});
}
#Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
Log.d (PURCHASE_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult (requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult (requestCode, resultCode, data);
} else {
Log.d (PURCHASE_TAG, "onActivityResult handled by IABUtil.");
}
}
#Override
public void onDestroy () {
super.onDestroy ();
if (mHelper != null) mHelper.dispose ();
mHelper = null;
}
Answer from comments above
It's better to catch the error and patch own code first. Trying to reproduce exact issue why was IAB not set up may not be possible, because of all kinds of modifications user can do on his device (could be bug with specific version of distro user is running, which is almost impossible to find out, or similar). As you suggested, using Analytics to find out how many users have this issue is good approach.
To answer original question, you can create Alpha or Beta versions in Developer Console and invite users with specific versions of Android you'd like to target into testing group. This was you could test issues and crashes directly from Dev. Console even when not directly being owner of the device.
I am a novice Android developer who has never used in-app billing before. So I have followed the steps outlined in Google's own documentation at http://developer.android.com/google/play/billing/billing_integrate.html.
I added the IInAppBillingService.aidl file under main/aidl/com.android.vending.billing and my structure matches countless screenshots I've seen online.
I added code very similar to the sample code to a part of my app that interacts with my online marketplace. A simplified version of that code, with extraneous stuff removed, is:
imports...
import android.content.ServiceConnection;
import com.android.vending.billing.IInAppBillingService;
public class intMarket extends Activity {
IInAppBillingService mService;
public static final String SHARED_PREFS_NAME="myAppSettings";
public static String purchaseToken = "";
ServiceConnection mServiceConn = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
#Override
public void onServiceConnected(ComponentName name,
IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intbrowser);
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new WebViewClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
myWebView.addJavascriptInterface(new WebAppInterface(this), "Android");
String tUrl = getString(R.string.urlMarket);
SharedPreferences settings = getSharedPreferences(SHARED_PREFS_NAME, 0);
myWebView.loadUrl(tUrl);
}
#Override
public void onDestroy() {
super.onDestroy();
if (mService != null) {
unbindService(mServiceConn);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1001) {
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
if (resultCode == RESULT_OK) {
try {
JSONObject jo = new JSONObject(purchaseData);
String sku = jo.getString("productId");
purchaseToken = jo.getString("purchaseToken");
}
catch (JSONException e) {
e.printStackTrace();
}
}
}
}
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
#JavascriptInterface
public void purchaseItem(Integer itemID, String itemName, BigDecimal itemPrice) {
// Credit Purchase
ArrayList<String> skuList = new ArrayList<String> ();
Bundle querySkus = new Bundle();
querySkus.putStringArrayList("ITEM_ID_LIST", skuList);
String purchaseCreditsResponse = "Success";
try {
Bundle skuDetails = mService.(3, getPackageName(), "inapp", querySkus);
Log.d("WPS", skuDetails.toString());
int response = skuDetails.getInt("RESPONSE_CODE");
Log.d("WPS", Integer.toString(response));
if (response == 0) {
ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
String thisItemID = "0";
for (String thisResponse : responseList) {
JSONObject object = new JSONObject(thisResponse);
String itemPrice = object.getString("price");
if (itemPrice.equals(itemPrice.toString()))
thisItemID = object.getString("productId");
}
if (Integer.parseInt(thisItemID) > 0) {
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), thisItemID, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
if (buyIntentBundle.getInt("RESPONSE_CODE") == 0) {
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
//startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
// purchaseToken
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Buying: " + itemName, Toast.LENGTH_LONG).show();
}
} else {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 001.", Toast.LENGTH_LONG).show();
}
} else {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 002.", Toast.LENGTH_LONG).show();
}
} catch (RemoteException e) {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 003.", Toast.LENGTH_LONG).show();
} catch (JSONException e) {
purchaseCreditsResponse = "Failure";
Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 004.", Toast.LENGTH_LONG).show();
}// catch (IntentSender.SendIntentException e) {
// purchaseCreditsResponse = "Failure";
// Toast.makeText(mContext, "Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 005.", Toast.LENGTH_LONG).show();
//}
// Purchase Report
// Report purchase and get response into purchaseResponse
// Todo
// Credit Consumption
// Todo
}
}
}
I then compiled a version of this and uploaded the APK to Google Play as an alpha test case and published it. I have been making updates and using ADB to install the new compiled APKs to my phone, but I continually get the following from this code in LogCat:
03-23 15:07:55.325 2958-3009/? D/WPS﹕ Bundle[mParcelledData.dataSize=48]
03-23 15:07:55.325 2958-3009/? D/WPS﹕ 5
Which, of course, indicates: BILLING_RESPONSE_RESULT_DEVELOPER_ERROR
I read in another SO post that you cannot test billing on a device whose main account is the same as the developer's account. So I spent several hours yesterday tracking down another phone and getting it setup using a completely separate account (and the only account on the device). I uploaded a new build to Google Play as an alpha test and published it. I waited for this other phone to get the new version and tried testing billing again. The phone reported this error message that I had built in:
Sorry, but there was a problem with your purchase. Please try again. If the problem persists, contact support. Error Code: 002.
Which indicates the same error I was running into before. This implies that there is a problem with my code, which would not surprise me.
I have not done anything with the big giant license code that appears in the Google Play developer panel because the above referenced documentation says nothing about it being required. But is it? If not, what about my code is causing this problem?
Verify that com.android.vending.BILLING is added to your manifest permissions.
Also verify that your app is signed with your production key and the version on Google Play should be the same as the version you are testing with.
Also have a look at this lightweight, straight forward library you can use on Github below:
https://github.com/anjlab/android-inapp-billing-v3
People comment that this is a bug in Google's billing API (maybe Google considers it a feature?).
Seems like you have to consume the purchaseToken, just as the guy here does:
String purchaseToken = o.optString("token", o.optString("purchaseToken"));
// Consume purchaseToken, handling any errors
mService.consumePurchase(3, getPackageName(), purchaseToken);
edit 4/15: Catching nullpointer in IabHelper appears to have stopped this problem. I am no longer seeing the exceptions being thrown, I'm going to accept this as an answer.
edit 4/04: A little bit of a deeper dive. There are try catch blocks that handle RemoteExceptions and JSONExceptions for the queryPurchases method, but no NullPointerException handling. What I am going to try is include NullPointer Exception handling so IabHelper looks like this when trying to querySkuDetails:
catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NullPointer while refreshing inventory.", e);
}
I just filed a bug on this:
https://code.google.com/p/marketbilling/issues/detail?id=114
edit 3/25: well, still receiving this crash... now it happens while trying to get a context at line 3 of the following excerpt from IabHelper:
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
logDebug("Querying owned items, item type: " + itemType);
logDebug("Package name: " + mContext.getPackageName());
This is frustrating because in my manifest I always use the full path name of my app for "name".
Example "com.myappname.blah.ClassName"
I've also tried passing this, MyClass.this, getApplicationContext() to mHelper. However they all produce the same NullPointer results randomly from devices in the wild. I also tried name=".MyClass" in the manifest. This is what it looks like currently:
mHelper = new IabHelper(MyClass.this, myKey);
edit 3/18/13: I am still receiving exceptions, even with the new IabHelper version deployed on 3/17.
I am starting to see a pattern here, that the crashes are all when trying to get a context when executing mContext.getPackageName(). I'm curious why this works on all of my test devices, and I can't reproduce this crash, and only seems to be on a small number of devices.
Here is the new crash:
java.lang.NullPointerException
at com.myapp.util.IabHelper.queryPurchases(SourceFile:836)
at com.myapp.util.IabHelper.queryInventory(SourceFile:558)
at com.myapp.util.IabHelper.queryInventory(SourceFile:522)
at com.myapp.util.IabHelper$2.run(SourceFile:617)
at java.lang.Thread.run(Thread.java:1019)
Caused by IabHelper...
line 836: logDebug("Package name: " + mContext.getPackageName());
edit 3/17/13: I see that there have been many bug fixes published over the past several months, I will try the latest code available here and see if this resolves the problem:
https://code.google.com/p/marketbilling/source/browse/v3/src/com/example/android/trivialdrivesample/util
In one of my apps, I am using the billing API and the boilerplate code included with it.
I am using the latest version of billing API available via the SDK manager as of 3/16/2013.
In my activity, I query the inventory using the following:
final List<String> skuList = new ArrayList<String>();
skuList.add("sku1");
skuList.add("sku2");
skuList.add("sku3");
if (skuList != null) {
if (skuList.size() > 0) {
try {
mHelper.queryInventoryAsync(true, skuList, mGotInventoryListener);
} catch (Exception e) {
ACRA.getErrorReporter().handleException(e);
}
}
}
I am receiving multiple NullPointerException reports in the wild from the IabHelper class for the following devices. I can't reproduce the issue and can't find any information regarding these crashes, and is the reason why I am posting this question.
I have countless other checks for nulls and try/catch blocks in the "developer facing" part of the billing API, including within onQueryInventoryFinished, so I know this exception is not being thrown from "my code" (because I'm not capturing crashes from any of my app's classes), but instead is being thrown from within the IabHelper itself. I have not modified the IabHelper other than this recommended fix: https://stackoverflow.com/a/14737699
Crash #1 Galaxy Nexus
java.lang.NullPointerException
at com.myapp.util.IabHelper.querySkuDetails(SourceFile:802)
at com.myapp.util.IabHelper.queryInventory(SourceFile:471)
at com.myapp.util.IabHelper$2.run(SourceFile:521)
at java.lang.Thread.run(Thread.java:856)
Caused by IabHelper...
line 802: Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), ITEM_TYPE_INAPP, querySkus);
Crash #2 Samsung GT-S5570L
java.lang.NullPointerException
at com.myapp.util.IabHelper.queryPurchases(SourceFile:735)
at com.myapp.util.IabHelper.queryInventory(SourceFile:465)
at com.myapp.util.IabHelper$2.run(SourceFile:521)
at java.lang.Thread.run(Thread.java:1019)
Caused by IabHelper...
line 735: Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(), ITEM_TYPE_INAPP, continueToken);
edit 4/15: Catching nullpointer in IabHelper appears to have stopped this problem. I am no longer seeing the exceptions being thrown, I'm going to accept this as an answer.
edit 4/04: A little bit of a deeper dive. There are try catch blocks that handle RemoteExceptions and JSONExceptions for the queryPurchases method, but no NullPointerException handling. What I am going to try is include NullPointer Exception handling so IabHelper looks like this when trying to querySkuDetails:
catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NullPointer while refreshing inventory.", e);
}
I just filed a bug on this:
https://code.google.com/p/marketbilling/issues/detail?id=114
Change
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
}
to
if (querySkuDetails) {
try {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
} catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NPE while refreshing inventory.", e);
}
}
Change
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreSubsSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
}
to
if (querySkuDetails) {
try {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreSubsSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
} catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NPE while refreshing inventory.", e);
}
}
You are probably using async operations. The current IabHelper is not safe in case you use the ...async methods. The problem is that in any moment an async operation is running dispose can be called on the main thread. In this case you will get NullPointerExceptions and IllegalStateExceptions.
Here is the patch fixing it:
Index: src/com/evotegra/aCoDriver/iabUtil/IabHelper.java
===================================================================
--- src/com/evotegra/aCoDriver/iabUtil/IabHelper.java (revision 1162)
+++ src/com/evotegra/aCoDriver/iabUtil/IabHelper.java (working copy)
## -86,7 +86,10 ##
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
- boolean mAsyncInProgress = false;
+ volatile boolean mAsyncInProgress = false;
+
+ // is set to true if dispose is called while a thread is running. Allows graceful shutdown
+ volatile boolean mDisposeRequested = false;
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
## -285,6 +288,12 ##
* disposed of, it can't be used again.
*/
public void dispose() {
+ // do not dispose while an async Thread is running. Will cause all kinds of exceptions.
+ // In this case dispose must be called from thread after setting mAsyncInProgress to true
+ if (mAsyncInProgress) {
+ mDisposeRequested = true;
+ return;
+ }
logDebug("Disposing.");
mSetupDone = false;
if (mServiceConn != null) {
## -827,6 +836,7 ##
logDebug("Ending async operation: " + mAsyncOperation);
mAsyncOperation = "";
mAsyncInProgress = false;
+ if (mDisposeRequested) IabHelper.this.dispose();
}
Or download the patch here.
http://code.google.com/p/marketbilling/issues/detail?id=139&thanks=139&ts=1375614409
Slightly modify the beginning of the queryPurchases method to look like this:
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
// Query purchases
//logDebug("Querying owned items, item type: " + itemType);
//logDebug("Package name: " + mContext.getPackageName());
boolean verificationFailed = false;
String continueToken = null;
do {
// logDebug("Calling getPurchases with continuation token: " + continueToken);
if(mDisposed || mService==null) return IABHELPER_UNKNOWN_ERROR;
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
itemType, continueToken);
Thanks to sebastie for pointing out the cause of this.
tmanthey patch also requires
mDisposeRequested = false;
after the disposing takes place
If you're getting this error on the emulator, it may be a very simple thing which happens in more than a half cases.
Check that you're using Google API SDK and not regular SDK!!!
The IabHelper is obsolete and has been replaced by the BillingClient.
See https://developer.android.com/google/play/billing/billing_library.html
I am using resolveActivityInfo to determine if my app was set as a home launcher:
PackageManager pm = getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_HOME);
userHomePackage = intent.resolveActivityInfo(pm, 0).packageName;
userHomeActivityClass = intent.resolveActivityInfo(pm, 0).name;
currentHomeLauncherName = intent.resolveActivityInfo(pm, 0).loadLabel(pm).toString();
it works great on emulator and three android devices I have on hands.
Recently I started getting error reports from my users and error log shows that resolveActivityInfo is failing. This happens only on a few phones running on android 2.1 update 1 as I can see. I've received already many positive comments on my app and a few negative because of this issue.
Any advice what could be wrong?
java.lang.NullPointerException
at android.os.Parcel.readException(Parcel.java:1224)
at android.os.Parcel.readException(Parcel.java:1206)
at android.content.pm.IPackageManager$Stub$Proxy.resolveIntent(IPackageManager.java:1418)
at android.app.ApplicationContext$ApplicationPackageManager.resolveActivity(ApplicationContext.java:2046)
at android.content.Intent.resolveActivityInfo(Intent.java:3790)
at com.myapp.myappname.Launcher.setAsHomeApplicationBeforeFroyo(Launcher.java:336)
OR
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp.myappname/com.myapp.myappname.Launcher}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2497)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2513)
at android.app.ActivityThread.access$2200(ActivityThread.java:119)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1864)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4370)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:862)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:620)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at android.os.Parcel.readException(Parcel.java:1224)
at android.os.Parcel.readException(Parcel.java:1206)
at android.content.pm.IPackageManager$Stub$Proxy.resolveIntent(IPackageManager.java:1418)
at android.app.ApplicationContext$ApplicationPackageManager.resolveActivity(ApplicationContext.java:2046)
at android.content.Intent.resolveActivityInfo(Intent.java:3790)
at com.myapp.myappname.Launcher.showHomeChooserDialog(Launcher.java:141)
at com.myapp.myappname.Launcher.showNextActivity(Launcher.java:122)
at com.myapp.myappname.Launcher.onCreate(Launcher.java:59)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2460)
... 11 more
java.lang.NullPointerException
at android.os.Parcel.readException(Parcel.java:1224)
at android.os.Parcel.readException(Parcel.java:1206)
at android.content.pm.IPackageManager$Stub$Proxy.resolveIntent(IPackageManager.java:1418)
at android.app.ApplicationContext$ApplicationPackageManager.resolveActivity(ApplicationContext.java:2046)
at android.content.Intent.resolveActivityInfo(Intent.java:3790)
at com.myapp.myappname.Launcher.showHomeChooserDialog(Launcher.java:141)
Kevin from TeslaCoil Software here. Sorry for the delay in getting back to you. This is what I'm using to get the Home component in my apps:
public static ComponentName getHomeComponent(PackageManager PM) {
Intent home_intent = new Intent("android.intent.action.MAIN");
home_intent.addCategory("android.intent.category.HOME");
home_intent.addCategory("android.intent.category.DEFAULT");
ComponentName cn = home_intent.resolveActivity(PM);
if (cn == null)
Log.v(TAG, "[Default] package:null");
else
Log.v(TAG, "[Default] package:" + cn.getPackageName() + " class:" + cn.getClassName());
return cn;
}
resolveActivity doesn't normally return null, as it seems to return the resolver activity if there is no default set. But on some phones or something it might return null just to keep things interesting. Perhaps resolveActivityInfo calls resolveActivity but doesn't handle the null case properly.
did some research with android source and i tend to agree now with Commonsware that my code is right. I actually redesigned it 3 weeks ago to use packagemanager.resolveActivity instead of intent.resolveactivity:
this.pm = context.getPackageManager();
try {
Intent homeintent = new Intent(Intent.ACTION_MAIN);
homeintent.addCategory(Intent.CATEGORY_HOME);
homeintent.addCategory(Intent.CATEGORY_DEFAULT);// seems not needed here since This is a synonym for
// including
// the CATEGORY_DEFAULT in your supplied Intent per doc
this.resolveInfo = pm.resolveActivity(homeintent, PackageManager.MATCH_DEFAULT_ONLY);
ActivityInfo activityInfo = resolveInfo.activityInfo;
userHomeLauncherPackage = activityInfo.packageName;
userHomeLauncherClass = activityInfo.name;
userHomeLauncherName = activityInfo.loadLabel(pm).toString();
if (userHomeLauncherClass.contains("ResolverActivity"))
userHomeLauncherName = "";
} catch (Exception e) {
throw new Exception(e);
}
It did not help so still getting these errors occasionally...
Based on the source code,
ComponentName Intent.resolveActivity (PackageManager pm)
or
ActivityInfo Intent.resolveActivityInfo (PackageManager pm, int flags)
call the same method defined in android.app.ContextImpl class:
ResolveInfo info = pm.resolveActivity(this, PackageManager.MATCH_DEFAULT_ONLY)
and it is defined like:
#Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
try {
return mPM.resolveIntent(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
so at this point i came to conclusion that I cannot do anything about this :(