2

I want to know how I can fire Android's LVL license verification in an asynchronous thread while my Phonegap app starts loading. If the verification results in a "no access" I want the app to close. I prefer this logic to the usual one-step-at-a-time setup. LVL takes quite a few seconds, and if an evil unlicensed user gets to see the app for a few seconds before it's closed, that is not a problem.

The problem is that I'm not big on Java.

My app I've got a PhoneGap (Cordova) paid app published in Google Play's that uses LVL Verification to check the app was paid for by the user. Working like a charm, except that the check takes about five seconds. Often, even the splash screen does not show for a few seconds, seemingly because of this.

So the user is stuck with a black screen for five seconds, and then gets the splash screen while the Java code is being loaded, and finally they get to see a default dimmed home screen until the Javascript is done. So I'm highly motivated to reduce this startup delay.

I've read a few comments that suggest using an asynchronous approach: start loading the URL right away, and run the license check as an asyncTask. But I don't know how to pass the webview to the asyncTask in order for the webview to be closed if the license is not valid.

My first try Since the license check itself is an async process, I've tried to first set the splash screen and load the URL, then do the license check. See code below. If the check returns 'not allowed', the callback should close the app. However, if I try this setup, somehow the LVL server always returns "valid", even if I set a forced response to 'invalid' or 'unknown' or anything else on Google Play.

I'm looking for either A or B: A. A way to get LVL to respond correctly. B. Another way to implement an async license check.

The code Below is an abstraction of the current code. DroidGap is the webview. Basically, onCreate kicks off the license check, and the license check callback either loads the app's HTML or closes the webview.

public class App extends DroidGap {

    public void onCreate(Bundle icicle) {
        super.setIntegerProperty("splashscreen", R.drawable.splash);
        super.loadUrl("file:///android_asset/www/index.html");

        mCheckerCallback = new LicenseCheckerCallback();
        checkAccess(mCheckerCallback);
    }

private class MyCheckerCallback implements LicenseCheckerCallback() {
    public void Allow() {
         //Do nothing
    }
    public void DontAllow(){
         finish();
    }

}
Community
  • 1
  • 1
Wytze
  • 7,844
  • 8
  • 49
  • 62

1 Answers1

1

Scott asked me if I found a solution. It's been too long to recall how I went about, but the below setup appears to have worked for me. Hope that helps!

package com.phonegap.mappingtheforest.helloworld;

import org.apache.cordova.DroidGap;

//import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings.Secure;
import android.util.Log;
import android.widget.Toast;

import com.android.vending.licensing.AESObfuscator;
import com.android.vending.licensing.LicenseChecker;
import com.android.vending.licensing.LicenseCheckerCallback;
import com.android.vending.licensing.ServerManagedPolicy;
//import com.phonegap.afforditfull.R;


public class App extends DroidGap { 

    private LicenseChecker mChecker;
    private LicenseCheckerCallback mLicenseCheckerCallback;
    private static final String BASE64_PUBLIC_KEY = "MyBase64PublicKey";
 // Generate 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
     //[my bunch of integers]
     };
    private AESObfuscator mObsfuscator;
    private String android_id;

    /** Called when the activity is first created. */ 
    @Override 
    public void onCreate(Bundle icicle) { 
         super.onCreate(icicle); 
         //setContentView(R.layout.main);
         super.loadUrl("file:///android_asset/www/index.html",1);
         //super.setStringProperty("loadingDialog", "Starting Afford-It...");
         super.setIntegerProperty("splashscreen", R.drawable.splash);
         android_id = Secure.getString(this.getContentResolver(), Secure.ANDROID_ID);
         mObsfuscator = new AESObfuscator(SALT, getPackageName(), android_id);
         ServerManagedPolicy serverPolicy = new ServerManagedPolicy(this,mObsfuscator);

         mLicenseCheckerCallback = new MyLicenseCheckerCallback();
         mChecker = new LicenseChecker(
             this, serverPolicy,
             BASE64_PUBLIC_KEY  // Your public licensing key.
             );
         mChecker.checkAccess(mLicenseCheckerCallback);  
    }

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
        public void allow() {
            if (isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            // Should allow user access.
            Log.w("LicenseChecker", "Allow");
            Intent i = new Intent(App.this, DroidGap.class);
            startActivity(i);
            finish();
        }

        public void dontAllow() {
            if (isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            Log.w("LicenseChecker", "Don't Allow");
            // Should not allow access. An app can handle as needed,
            // typically by informing the user that the app is not licensed
            // and then shutting down the app or limiting the user to a
            // restricted set of features.
            // In this example, we show a dialog that takes the user to Market.
            showDialog(0);

        }

        @Override
        public void applicationError(ApplicationErrorCode errorCode) {
            if (isFinishing()) {
                // Don't update UI if Activity is finishing.
                return;
            }
            toast("Error: " + errorCode.name());

        }
    }
    @Override
    protected Dialog onCreateDialog(int id) {
        // We have only one dialog.
        return new AlertDialog.Builder(this)
                .setTitle("Application Not Licensed")
                .setCancelable(false)
                .setMessage(
                        "This application is not licensed. Please purchase it from Android Market")
                .setPositiveButton("Buy App",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                Intent marketIntent = new Intent(
                                        Intent.ACTION_VIEW,
                                        Uri.parse("market://details?id=com.phonegap.mappingtheforest.afforditpaid" + getPackageName()));
                                startActivity(marketIntent);
                                finish();
                            }
                        })
                .setNegativeButton("Exit",
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog,
                                    int which) {
                                finish();
                            }
                        }).create();
    }   

    public void toast(String string) {
        Toast.makeText(this, string, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    mChecker.onDestroy();
  }
}
Wytze
  • 7,844
  • 8
  • 49
  • 62