Question

I'm trying to manage a free/paid app by having the paid version be a simple licensing server.

The paid app has a receiver:

public class LicenseRequest extends BroadcastReceiver
{
    private static final String TAG = LicenseRequest.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent)
    {
        if (!intent.getAction().equals(App.LICENSE_REQUEST))
        {
            return;
        }
        Intent licenseRequest = new Intent(context, LicenseService.class);
        context.startService(licenseRequest);
    }
}

that calls an IntentService:

public class LicenseService extends IntentService
{
    private static final String TAG = LicenseService.class.getSimpleName();

    private LicenseCheckerCallback mLicenseCheckerCallback;
    private LicenseChecker mChecker;

    ...

    public LicenseService()
    {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent)
    {
        // Try to use more data here. ANDROID_ID is a single point of attack.
        String deviceId = ...;

        // Library calls this when it's done.
        mLicenseCheckerCallback = new MyLicenseCheckerCallback();
        // Construct the LicenseChecker with a policy.
        mChecker = new LicenseChecker(this, new ServerManagedPolicy(this, new AESObfuscator(SALT, getPackageName(), deviceId)),
                BASE64_PUBLIC_KEY);
        mChecker.checkAccess(mLicenseCheckerCallback);
    }

    private class MyLicenseCheckerCallback implements LicenseCheckerCallback
    {   
        public void allow(int policyReason)
        {
            Log.i(TAG, "License Accepted");
            Intent i = new Intent();
            i.setAction(App.LICENSE_RECEIVER);
            i.putExtra(App.LICENSE_RESULT, App.LICENSE_ALLOW);
            sendBroadcast(i);
//          mChecker.onDestroy();
        }

        public void dontAllow(int policyReason)
        {
            Log.e(TAG, "License Denied");
            Intent i = new Intent();
            i.setAction(App.LICENSE_RECEIVER);
            i.putExtra(App.LICENSE_RESULT, App.LICENSE_DISALLOW);
            sendBroadcast(i);
//          mChecker.onDestroy();
        }

        public void applicationError(int errorCode)
        {
            Log.i(TAG, "LR Error");
            Intent i = new Intent();
            i.setAction(App.LICENSE_RECEIVER);
            i.putExtra(App.LICENSE_RESULT, App.LICENSE_ERROR);
            sendBroadcast(i);
//          mChecker.onDestroy();
        }
    }

//    @Override
//    public void onDestroy() {
//        super.onDestroy();
//        mChecker.onDestroy();
//    }
}

There are two ways I've tried handling the onDestroy(). If I call it within the LicenseService.onDestroy() I get:

04-11 15:35:13.604: W/MessageQueue(30689): Handler (android.os.Handler) {41388638} sending message to a Handler on a dead thread
04-11 15:35:13.604: W/MessageQueue(30689): java.lang.RuntimeException: Handler (android.os.Handler) {41388638} sending message to a Handler on a dead thread

This is due to the lifecycle of the IntentService ending before the callback I believe.

If I call it within the callback:

04-11 15:49:52.554: E/ActivityThread(32595): Service app.LicenseService has leaked ServiceConnection com.google.android.vending.licensing.LicenseChecker@41385168 that was originally bound here

This one I don't understand. I think this is a fairly common way of managing a pay version, how have folks managed the life cycle here? Thanks!

Was it helpful?

Solution

The answer is to use a ContentProvider and supply a simple MatrixCursor on the query. BroadcastReceivers are impossible due to the requirement that they must be explicitly run by the user first, which in an app that doesn't launch is impossible. The ContentProvider is a little odd but simpler too.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top