Monday, August 1, 2011

Tweaking the Licensing validation logic - again!

I appear to *still* be having issues with the licensed function. I can't get into the questions activity, which does this at the beginning:


if (false == SharedPreferencesUtil.getLicensed(appState)) {
Log.d(TAG, ">>>>>>>>> exiting question activity - unlicensed");
finish();
}


I didn't see that brief dialog box which shows it's checking the app. Ok, I reran it, and there it was. So, the problem might be the shared preferences isn't getting updated properly, or I'm accessing it incorrectly.

Heres the code:


private void handleResponse(boolean licensed) {

AppState appState = (AppState) ValidateLicense.this.mActivity
.getApplication();

// appState.licensed = true;
// InitUtils.saveState(appState);

SharedPreferencesUtil.setLicensed(appState, licensed);

ValidateLicense.this.mProgressDialog.cancel();

ValidateLicense.this.mProgressDialog.dismiss();

mChecker.onDestroy();

}


And I'm calling with a true - ist it a different application context?

Let's try an experiment - will use the legacy appSate code and the new SharedPreferencesUtil code simultaneously:


private void handleResponse(boolean licensed) {
AppState appState = (AppState) ValidateLicense.this.mActivity
.getApplication();

appState.licensed = true;
InitUtils.saveState(appState);

SharedPreferencesUtil.setLicensed(appState, licensed);

ValidateLicense.this.mProgressDialog.cancel();

ValidateLicense.this.mProgressDialog.dismiss();

mChecker.onDestroy();
}

Hmm...this time, it didn't go through.

Actually, there was a network issue, the Panera issue, but it fell through. So, it's not a case of the legacy code - they both worked. It's that tricky validation logic again.

Looking back through logcat, we can see this:

D/MyLicenseCheckerCallback(17029): >>>>>>>>>>>>> don't allow
D/InitUtils(17029): appState.currentQuestion: 0
D/dalvikvm(17029): GC_EXTERNAL_ALLOC freed 200K, 49% free 2850K/5511K, external 2070K/2137K, paused 45ms
W/MessageQueue(17029): Handler{40540660} sending message to a Handler on a dead thread
W/MessageQueue(17029): java.lang.RuntimeException: Handler{40540660} sending message to a Handler on a dead thread
W/MessageQueue(17029): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:196)
W/MessageQueue(17029): at android.os.Handler.sendMessageAtTime(Handler.java:457)
W/MessageQueue(17029): at android.os.Handler.sendMessageDelayed(Handler.java:430)
W/MessageQueue(17029): at android.os.Handler.post(Handler.java:248)
W/MessageQueue(17029): at com.android.vending.licensing.LicenseChecker$ResultListener.verifyLicense(LicenseChecker.java:208)
W/MessageQueue(17029): at com.android.vending.licensing.ILicenseResultListener$Stub.onTransact(ILicenseResultListener.java:60)
W/MessageQueue(17029): at android.os.Binder.execTransact(Binder.java:320)
W/MessageQueue(17029): at dalvik.system.NativeStart.run(Native Method)

I don't exactly know what that means, but it looks like a thread was dead and something to do with licensing tried to talk to it.

Let's look at the code...


handleResponse(false);

displayLicensingCallbackResult(UNLICENSED_MESSAGE);


private void handleResponse(boolean licensed) {
AppState appState = (AppState) ValidateLicense.this.mActivity
.getApplication();

appState.licensed = true;
InitUtils.saveState(appState);

SharedPreferencesUtil.setLicensed(appState, licensed);

ValidateLicense.this.mProgressDialog.cancel();

ValidateLicense.this.mProgressDialog.dismiss();

mChecker.onDestroy();
}


Possibly the mChecker.onDestroy()?

Well, let's set the status to unlicensed, and see if we can reproduce this.

Hmm...no dialog showing the problem, goes to the start display, and I can'f find any disallow message in logcat.

D/AndroidRuntime(17115): >>>>>> AndroidRuntime START com.android.internal.os.RuntimeInit <<<<<<
D/AndroidRuntime(17115): CheckJNI is OFF
D/dalvikvm(17115): creating instr width table
D/AndroidRuntime(17115): Calling main entry com.android.commands.am.Am
I/ActivityManager( 101): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.kanjisoft.jlpt4.full/.BootStrapActivity } from pid 17115
D/AndroidRuntime(17115): Shutting down VM
D/dalvikvm(17115): GC_CONCURRENT freed 102K, 69% free 320K/1024K, external 0K/0K, paused 1ms+0ms
I/ActivityManager( 101): Start proc com.kanjisoft.jlpt4.full for activity com.kanjisoft.jlpt4.full/.BootStrapActivity: pid=17124 uid=10066 gids={3003, 1015}
I/AndroidRuntime(17115): NOTE: attach of thread 'Binder Thread #3' failed
D/jdwp (17115): Got wake-up signal, bailing out of select
D/dalvikvm(17115): Debugger has detached; object registry had 1 entries
I/ActivityManager( 101): Starting: Intent { cmp=com.kanjisoft.jlpt4.full/com.jlptquiz.app.StartActivity } from pid 17124
D/InitUtils(17124): appState.currentQuestion: 0
D/szipinf (17124): Initializing inflate state
D/dal

It went into start activity without checking - wait - here's the code in bootstrap activity:

AppState appState = (AppState) this.getApplication();
boolean licensed = SharedPreferencesUtil.getLicensed(appState);

if (false == licensed) {
validateLicense();
}


So, yeah, that's ok. But, why do we get this:

V/RenderScript_jni(16963): surfaceDestroyed
I/ActivityManager( 101): Displayed com.kanjisoft.jlpt4.full/com.jlptquiz.app.StartActivity: +1s397ms (total +1s615ms)
D/InitUtils(17124): appState.currentQuestion: 0
I/ActivityManager( 101): Starting: Intent { cmp=com.kanjisoft.jlpt4.full/com.jlptquiz.app.QuestionActivity } from pid 17124
D/InitUtils(17124): appState.currentQuestion: 0
D/QuestionActivity(17124): >>>>>>>>> SharedPreferencesUtil.getLicensed(appState - unlicenced!
D/QuestionActivity(17124): >>>>>>>>> exiting question activity - unlicenced
D/dalvikvm(17124): GC_EXTERNAL_ALLOC freed 820K, 54% free 2905K/6215K, external 3119K/3119K, paused 33ms
D/InitUtils(17124): appState.currentQuestion: 101
W/InputManagerService( 101): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stu

Some how, shared preferences values changed? But they were just preserved in the last test. It's a mystery. It would solve this problem and another one, which is how often to check licensing - if I just checked it every time?

Let's try this:

One line in the bootstrap:

validateLicense();

Now, that works just fine. But what if they're offline, or in a Panera-type gateway situation? Well, first let's check the licensed code again.

How do I simulate the Panera on-a-local-network-but-gateway-blocking situation? That will be easy enough, I'll just go there this afternoon. I go there every day anyway.

Ok, I'm going to leave it at that for now. It's definitely simper just to check it every time.

Let's test the offline. Put it in airplane mode...

Black. It goes to The Dark Place, and stays there.

Let's check the log...

Well, we have this:

Activity com.jlptquiz.app.StartActivity has leaked IntentReceiver com.jlptquiz.app.StartActivity$2$1@40640708 that was originally registered here. Are you missing a call to unregisterReceiver()?
E/ActivityThread(17243): android.app.IntentReceiverLeaked: Activity com.jlptquiz.app.StartActivity has leaked IntentReceiver com.jlptquiz.app.StartActivity$2$1@40640708 that was originally registered here. Are you missing a call to unregisterReceiver()?
E/ActivityThread(17243): at android.app.LoadedApk$ReceiverDispatcher.(LoadedApk.java:756)
E/ActivityThread(17243): at android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:551)
E/ActivityThread(17243): at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:798)
E/ActivityThread(17243): at android.app.ContextImpl.registerReceiver(ContextImpl.java:785)
E/ActivityThread(17243): at android.app.ContextImpl.registerReceiver(ContextImpl.java:779)
E/ActivityThread(17243): at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:318)
E/ActivityThread(17243): at com.jlptquiz.app.StartActivity$2.onClick(StartActivity.java:282)
E/ActivityThread(17243): at android.view.View.performClick(View.java:2485)
E/ActivityThread(17243): at android.view.View$PerformClick.run(View.java:9080)
E/ActivityThread(17243): at android.os.Handler.handleCallback(Handler.java:587)
E/ActivityThread(17243): at android.os.Handler.dispatchMessage(Handler.java:92)
E/ActivityThread(17243): at android.os.Looper.loop(Looper.java:130)
E/ActivityThread(17243): at android.app.ActivityThread.main(ActivityThread.java:3683)
E/ActivityThread(17243): at java.lang.reflect.Method.invokeNative(Native Method)
E/ActivityThread(17243): at java.lang.reflect.Method.invoke(Method.java:507)
E/ActivityThread(17243): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
E/ActivityThread(17243): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
E/ActivityThread(17243): at dalvik.system.NativeStart.main(Native Method)
V/RenderScript_jni(16963): surfaceCreated
V/RenderScript_jni(16963): surfaceChanged

But, I don't think that's the problem.

Anyway, I'm getting rid the listener that triggers that pesky message. That was only to allow a change to the level to be reflected on the initial start display, but now that I'm sure I'm going with separate apps, that's not going to be changeable any more.

Ok, looking at StartActivity, when it's not connected, it should now being doing the same thing as when it's successfully license-validated, which is launch the start activity.

There we go - this code covers the three states of

1) Connected - launch the licence check
2) Disconnected and previously license-validated (launch start)
3) Disconnected and previously unlicensed - display message and exit on ok.

Disconnected and previously license-validated (launch start)


public void valdidateLicense(Activity activity) {

Log.d(TAG, "================ >validate license <==================");

this.mActivity = activity;

if (Utils.isOnline(this.mActivity)) {

mProgressDialog = ProgressDialog.show(activity, "",
"Validating license..", true);

mProgressDialog.setCancelable(false);

checkLicense();

} else {

// not online
AppState appState = (AppState) this.mActivity
.getApplicationContext();

// if licensed, allow

if (true == SharedPreferencesUtil.getLicensed(appState)) {

Intent i = new Intent(this.mActivity,
StartActivity.class);

mActivity.startActivity(i);

return;

} else {

// Network error, display message to connect

// add a neutral button to the alert box and assign a click
// listener
AlertDialog.Builder alertbox = new AlertDialog.Builder(
ValidateLicense.this.mActivity);

alertbox.setMessage(NETWORK_ERR);
alertbox.setCancelable(false);

alertbox.setNeutralButton("Ok",
new DialogInterface.OnClickListener() {

// click listener on the alert box
public void onClick(DialogInterface arg0, int arg1) {
ValidateLicense.this.mActivity.finish();
}
});

// show it
alertbox.show();
}
}
}

If ti's connected, well, there's a whole 'nother set of conditions.

Ok, now let's get rid of the code that launches the notification of level change to the leaked listener.

// Intent intent = new Intent("my.db.updated");
// getApplicationContext().sendBroadcast(intent);

Gonzo.

Ok, that's it for now. Let's do some testing.

No comments:

Post a Comment