Sunday, July 31, 2011

Android project setup - paid, free, core and licensing

Ok, we're making progress. We've just set up the licensing validation to be called from "bootstrap" activity, and come up with a nice, clean solution to handling licensing validation in only the paid program. But, there's still plenty of work to do. Since google doesn't allow upload of free apps to the marketplace if they have the validation library, we need to separate the licensing validation code into a separate library, which will be only included int the paid versions.

Let's create that project now, create the class and copy in the ValidateLicense class code. Ok, we need to add the licensing library to the project for these classes:

import com.android.vending.licensing.AESObfuscator;
import com.android.vending.licensing.LicenseChecker;
import com.android.vending.licensing.LicenseCheckerCallback;
import com.android.vending.licensing.ServerManagedPolicy;

Here's how it's done, from somewhere on google:

To download the LVL component into your development environment, use the Android SDK and AVD Manager. Launch the Android SDK and AVD Manager and then select the "Market Licensing" component, as shown in the figure below. Accept the terms and click Install Selected to begin the download.

It's already installed - ah, here we go:

After downloading the LVL to your computer, you need to set it up in your development environment, either as an Android library project or by copying (or importing) the library sources directly into your existing application package.

In general, using the LVL as a library project is recommended, since it lets you reuse your licensing code across multiple applications and maintain it more easily over time. Note that the LVL is not designed to be compiled separately and added to an application as a static .jar file.

Ok, but, how to import it as a library again? Let's see project settings on the old project...

Ok, it look like you add it as a source library. In my case, it's under workplace/marketlicensing/library/src

Ah, there's the project, oddly name com_android_vending_licensing. So, now I just add it as a library project, but how? Ah, there it is, the add project. There we go.

And go to project, properties, android and check off that it's a library.

Ah, there's a problem. In order to kill the called app, it needs a backwards reference to the activity that called it, which is different. Why does it need that? I just thought it was a generic activity. Ah good, I just needed to delete the import.

Ok, great! Now I'm going to delete the reference code for that call from the common source...

And also add the common code as a another shared project, just like the downloaded licensing library...

ctrl-shift-o (for mac) to add back in all the required imports...

Now, add my new project library to the "shell" app as a project. Hmm, why isn't the regular library showing up on the shared projects list? How did I add it, again?

Ah, here it is:

In the Package Explorer, right-click the dependent project and select Properties.
In the Properties window, select the "Android" properties group at left and locate the Library properties at right.
Click Add to open the Project Selection dialog.
From the list of available library projects, select a project and click OK.
When the dialog closes, click Apply in the Properties window.
Click OK to close the Properties window.

That's what I was missing. I'll go back and fix it on my licensing project. The trick is to remember "Android". I just posted that!

Ah, perfect. Now let's go ahead and add the new licensing project to the "shell" project.

And, finally remove the android licensing library it from the "core" project...

And delete the ValidationLicense.java class.

Whew!

Ah, and change the import statement on the shell bootstrap activity to point at the new library project.

Man. That was intense. I just got hung up on how to include the hew library project. I should read my own blog postings!

Ok, how about a test?

Perfect! I got my error message. NOW we can look ut uploading the new app. I'll tackle that in my next blog.

Android library setup & licensing

Ok, we've got our main project set up as a library, and have set up a mini-project which just invokes virtually all the functionality of the library. There's one small issue to clear up before we tackle bring the licensing - somehow or other, one of the headings of the project has gotten messed up.

One way to get rid of it is get rid of the heading in the layout. Anyway, this will flag where it gets updated. I can put it back in later.

Ah, ok, I had changed the heading in strings.xml, to this:

Jlpt Vocabulary Quiz Level 4

And in the manifest, I had been referencing that same heading as the app label.

So what I can do is change the label to something easy and short.



Jlpt 4 Quiz


It will pull all the other strings from the library project.

Ok, let's see how this looks...yup. Great.

Hmm...the zero/zero on the start and end quiz numbers. Did they get stored as zero? This is a bit disturbing. Somehow I thought I had eliminated that.

I'm going to at least put a safegaurd in. Once I read it from memory, if it's zeros, I'm going to set the start to 1 and the end to the default quiz size.

Ok, that's taken care. Now, the licensing. Unfortunately, Google doesn't let you upload the licensing code if it's free. So, I think I need to pull that into a different library. Then, in the free app projects, I don't include that library, and in the paid apps, I do. I'm just struggling a bit with how to work that in.

Well, first off, it can't be accessed from the "main" library, as that's going to be used by both the free and paid versions. Also, it can't be accessed from the free version of the shell projects. So, it's only accessible from the paid versions of the shell projects. So, each shell project should look something like this:

public class BootStrapActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// validation code goes here

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

startActivity(i);

}
}


So, let's just move it from the call to validation into this code.


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



private void validateLicense() {
new ValidateLicense().valdidateLicense(this);
}


Oh, and I'm moving a lot of the primitive state out of AppState, which is just a duplicate of shared preferences.


public static boolean getLicensed(AppState appState){

SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(appState);


if (sharedPreferences.contains(InitUtils.LICENSED)) {
return sharedPreferences.getBoolean(InitUtils.LICENSED, false);
}

return false;
}


public static void setLicensed(AppState appState, boolean licensed) {

SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(appState);

SharedPreferences.Editor prefEditor = sharedPreferences.edit();

prefEditor.putBoolean(InitUtils.LICENSED, licensed);

prefEditor.commit();

}

Ok, now we have code like this:

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


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

startActivity(i);

private void validateLicense() {

// this uses a callback to inform the app if the license is valid
new ValidateLicense().valdidateLicense(this);
}


Now, we have to jump back into the validate code and redo it to refer back to the BootStrapActivity instead of the StartActivity.


And change this in the success callback:

SharedPreferencesUtil.setLicensed(appState, true);


We still have to leave the awkward initiation of the download in the callback validation, so it doesn't get kicked off before an unlicensed callback kill the app.

Ok, let's take a look:

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

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

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

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

startActivity(i);
}


Ok, clean up the interior references,

if (false == SharedPreferencesUtil.getLicensed(appState)) {
finish();
}


Ok, let's get a test in...

Hmm...it doesn't seem to have called the validation. I think what happened is, because I haven't gone through Panera's gateway, I'm connected to a network, but it's not getting a response from the server. This normally display a dialog box saying it's checking but in this case, it just called the start activity, probably killing the dialog.

Ok. Here's the solution. In the paid version, I can just call the start activity on a success in the validation. This will allow me to move the download of the media files back into start activity, where it belongs, and with only one call. Much cleaner. And in the free version, I just call StartActivity directly.


In the validateLicense:

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

I can also check if it's already validated in the BootStrapActivity, and skip the validation:


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

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

else {

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

}

Let's try it.

Ah, much better. I"m getting the "MarketNotManaged" message, which means I haven't added the app yet. Ok, let's upload of draft version of the apk.

This post is getting a bit long, so I'll address that in the next one.

Quick detour into PHP

In this blog, we check out an error I'm getting in my online kanji learning program, at kanjisoft.com/lamp/fp. I did this as the final project for a course I took in PHP at Harvard Extension school, taught by the amazing David Malan. Btw, he and Daniel Armandez also teach a course in mobile programming, and it's available free online. Anyway, I had done this page as a final project, and I just moved it up to my site. But, someone wanted to check it out, and I logged into it and received this error:

Warning: mysql_connect() [function.mysql-connect]: Access denied for user 'myname'@'localhost' (using password: YES) in /home4/kanjisof/public_html/lamp/fp/includes/functions.php on line 40
Could not connect to database Access denied for user 'myusername'@'localhost' (using password: YES)

So, let's see if we can figure out why access is getting denied. Probably the fastest thing to do is google it...

Ok, it looks like I might have an invalid password or username when I'm accessing in.

Let's establish what the username and password really are. Well, ok, first, let's look at the code and find out what we're trying to login as.

Ok, I'm on the server and here's the line:


if(($connection = mysql_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD)) === FALSE)

So, where is the name and password? It's not in this file. Let's go up a directory and grep it..

grep -ir DB_USERNAME *

There it is, we'll call it "myusername".

How about the password?

Ok, we'll call it "mypassword".

Ok. So, why does it say password is yes? Let's get into phpmyadmin...

Ok, I need to know what the database name is.

Here it is:

if(mysql_select_db(DATABASE_NAME, $connection) === FALSE)
{
exit( "Could not select database: " . mysql_error($connection) );
}

Ok, we know the drill. Back out and grep -ri DATABASE_NAME *.

Actually, it's likely to be where the other two were, in config.php.

Yup, it's my_databasename

Ok, now what?

Well, so the user name in my code doesn't match the current listed user, so that's probably the issue.

So, two questions - why did the username change, and why is it using password "Yes".

I think I'll call bluehost, they probably get this question a dozen times a day.

Ok, I'm on hold. ok, yeah, the username was the issue. it's strange, I didn't change anything, and the tech said they hadn't done anything on their end.

Anyway, it's all better. Check it out kanjisoft.com/lamp/fp. And that's a wrap :)

Android - creating a library project

Ok. The seemingly endless trek to getting this app launched continues. In my last post, we discovered that it would probably be worth our while to set up a project library in order to manage the paid vs. free version, as well as the multiple levels. Let's take a look at what the docs say about it.

http://developer.android.com/guide/developing/projects/projects-eclipse.html

Included topics are:

Setting up a Library Project
Referencing a Library Project

You can also designate an Android project as a library project, which allows it to be shared with other projects that depend on it. Once an Android project is designated as a library project, it cannot be installed onto a device.


Setting up a Library Project

A library project is a standard Android project, so you can create a new one in the same way as you would a new application project.

When you are creating the library project, you can select any application name, package, and set other fields as needed, as shown in figure 1.

Next, set the project's properties to indicate that it is a library project:

In the Package Explorer, right-click the library project and select Properties.
In the Properties window, select the "Android" properties group at left and locate the Library properties at right.
Select the "is Library" checkbox and click Apply.
Click OK to close the Properties window.




The new project is now marked as a library project. You can begin moving source code and resources into it, as described in the sections below.

You can also convert an existing application project into a library. To do so, simply open the Properties for the project and select the "is Library" checkbox. Other application projects can now reference the existing project as a library project.



// This is what we want to do.

Creating the manifest file

A library project's manifest file must declare all of the shared components that it includes, just as would a standard Android application. For more information, see the documentation for AndroidManifest.xml.

For example, the TicTacToeLib example library project declares the Activity GameActivity:


...

...

...



// Ok, I've got all that declared already.



Referencing a library project

If you are developing an application and want to include the shared code or resources from a library project, you can do so easily by adding a reference to the library project in the application project's Properties.

To add a reference to a library project, follow these steps:

In the Package Explorer, right-click the dependent project and select Properties.
In the Properties window, select the "Android" properties group at left and locate the Library properties at right.
Click Add to open the Project Selection dialog.
From the list of available library projects, select a project and click OK.
When the dialog closes, click Apply in the Properties window.
Click OK to close the Properties window.

As soon as the Properties dialog closes, Eclipse rebuilds the project, including the contents of the library project.

// That won't be a problem.

Declaring library components in the the manifest file

In the manifest file of the application project, you must add declarations of all components that the application will use that are imported from a library project. For example, you must declare any , , , , and so on, as well as , , and similar elements.

Declarations should reference the library components by their fully-qualified package names, where appropriate.

For example, the TicTacToeMain example application declares the library Activity GameActivity like this:


...

...

...



For more information about the manifest file, see the documentation for AndroidManifest.xml.
↑ Go to top


// Ok, I'll need to make the names fully qualified. But other than that, there doesn't seem to be too much heavy lifting as far as converting to a library project.

Now the question is, how to access the library project? The basic difference will really be that the validation code can't be included. So, the activity, StartActivity that calls the validation, will have to be a separate one. Or, I could have a Validate activity at the very begining, which calls the Start activity if successful. Or a BootStrapActivity that launches the start activity if it's the free version, and the validation activity if if paid. And the validation will launch the startActivity.

Ok, well, I suppose I should make sure that structure works before breaking out into separate projects.

package com.jlptquiz.app;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class BootStrapActivity extends Activity {

private static final String TAG = "BootStrapActivity";

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.start_layout);
// Get the title text view

// pass control to Start activity
Intent i = new Intent(this,
StartActivity.class);

startActivity(i);

}
}

Ok, and change the start activity int the manifest, and add this:

android:screenOrientation="portrait">

Ok, let's see what happens...

Permission denial?

[2011-07-31 06:11:17 - JlptQuizApp] Starting activity com.jlptquiz.app.BootStrapActivity on device HT9CSP820421
[2011-07-31 06:11:18 - JlptQuizApp] ActivityManager: Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.jlptquiz.app/.BootStrapActivity }
[2011-07-31 06:11:18 - JlptQuizApp] ActivityManager: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.jlptquiz.app/.BootStrapActivity } from null (pid=10618, uid=2000) requires null

And form logcat, ver similar...

I/ActivityManager( 101): Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.jlptquiz.app/.BootStrapActivity } from pid 10618
W/ActivityManager( 101): Permission denied: checkComponentPermission() reqUid=10061
W/ActivityManager( 101): Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.jlptquiz.app/.BootStrapActivity } from null (pid=10618, uid=2000) requires null

A post on SO says try uninstalling.

Hmm, still not good.

Well, let's change it back to start activity - ok, that works.

Ok, the problem turned out to be I had the activity named twice, once as the starting activity and once with a standard entry. So, *that* problem is solved.

Now, let's just give every activity a package name in the manifest.

Now, let's add the intent to to call StartActivity...

Crash, but that may be ok...yeah, I just forgot to add back in the declaration of StartActivity.

Ok, it seems to work ok, although the quiz start and end numbers somehow got reset to zero. I'll get to that later.

Ok, let's try out with all the startActivity code deleted, just this:


// pass control to Question activity
Intent i = new Intent(BootStrapActivity.this,
StartActivity.class);

startActivity(i);

Good! We're well on our way.

Ok, the next step is to make a copy of the project.

Now, let's make this a library, by following the steps described earlier in the post:


Next, set the project's properties to indicate that it is a library project:

In the Package Explorer, right-click the library project and select Properties.
In the Properties window, select the "Android" properties group at left and locate the Library properties at right.
Select the "is Library" checkbox and click Apply.
Click OK to close the Properties window.

That was easy.

Now, to reference it in the copied project...

To add a reference to a library project, follow these steps:

In the Package Explorer, right-click the dependent project and select Properties.
In the Properties window, select the "Android" properties group at left and locate the Library properties at right.
Click Add to open the Project Selection dialog.
From the list of available library projects, select a project and click OK.
When the dialog closes, click Apply in the Properties window.
Click OK to close the Properties window.

Ok, that copy is kind of full of errors. I think I'll just start a new Android project.

Ok, what should I specify as the min sdk?

http://phandroid.com/2010/11/02/77-of-android-phones-running-2-1-or-higher-1-5-close-to-being-obsolete/

As of Nov 2010, 9 months ago:

New platform figures have eeked out of Google today. According to their latest two-week ping leading up to November 1st, 77% of all Android phones run Android 2.1 or Android 2.2 (and Android 2.3, I suppose.) Specifically, 40.8% are running 2.1, and 36.2% are on 2.2. That still leaves 23% of phones on Android 1.6 or lower, but at least that number is getting smaller as each month goes by. 15% are on Android 1.6 while only 7.9 are on Android 1.5. With a majority of Motorola MOTOBLUR device owners around the world eventually being stepped up to 2.1, we don’t expect to see Android 1.5 lingering around too much longer. Google for the chart.

So, at that time, there were 23% 1.6 or lower, and 8% 1.5. So 1.5 is definitely out. Should I support 1.6? I'll go with that.

Ok. Ah, I see. When you add an imported library, Eclipse gives you access to the source of that library, so you don't have to switch to it. That's helpful.

Ok, we're getting there. Now, there were a few things I needed to change in the manifest.

Basically, I need to copy the manifest from the library into this, I think.

Ok, actually, the "android:installLocation="preferExternal" feature of the manifest is only support in 2.2 and higher. Hmm...

Well, first let's get this working, then we'll figure out what to do about that. Leave the target at 2.2 for now.

i wonder if in the mainifest, when it says


if it finds the app name from the import project?

Well, let's just manually fill it in for now.

Ok, there were a few other things I needed to think about.

I thought I saved it...

Well, let's see.

Nag Questions - we can take that from the library for now

App Label

Package name

And the validation code.

Ok, well, lt's just see what happens when we run this

Crash -

E/AndroidRuntime(12057): java.lang.RuntimeException: Unable to instantiate application com.kanjisoft.jlpt4.full.AppState: java.lang.ClassNotFoundException: com.kanjisoft.jlpt4.full.AppState in loader dalvik.system.PathClassLoader[/mnt/asec/com.kanjisoft.jlpt4.full-1/pkg.apk]
E

Ok, this has to do with naming the application, which I extend with an object called AppState.

I just need to give it the correct package name...


And try again. Crash...

/ActivityManager( 101): Starting: Intent { cmp=com.kanjisoft.jlpt4.full/com.jlptquiz.app.StartActivity } from pid 12126
I/LicenseChecker(12126): Binding to licensing service.
D/AndroidRuntime(12126): Shutting down VM
W/dalvikvm(12126): threadid=1: thread exiting with uncaught exception (group=0x40015560)
E/AndroidRuntime(12126): FATAL EXCEPTION: main
E/AndroidRuntime(12126): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.kanjisoft.jlpt4.full/com.jlptquiz.app.StartActivity}: java.lang.RuntimeException: native typeface cannot be made
E/AndroidRuntime(12126): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1647)
E/AndroidRuntime(12126): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
E/AndroidRuntime(12126): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
E/AndroidRuntime(12126): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
E/AndroidRuntime(12126): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(12126): at android.os.Looper.loop(Looper.java:130)
E/


The typface is an issue? But that's include in the library. Or isn't it? Let's copy it in from

Ok, it got as far as the validation error...cool. Before I add it to the server, let's temporarily disable it in the code to see if the app works.

I have some internal checks on licensing, need to disable those...

Ok, that looks good. It's working. This was actually easier than I expected. This will make it much, much simpler to manage multiple apps. There's still a lot of work to do, but the main horror-show threat, having to constantly modify versions of the source code with each release, has been handled. Nice :)

Saturday, July 30, 2011

Renaming the app package - a challenge

So, as we discovered in my post last night, renaming the package of an application is far from being a trivial process. Somehow or other, the whole R file thing manages to jam up the works. It doesn't seem to find it under the new package name.

One possible approach is to pull it out of svn to another project, as kind of sandbox, and then rename, then delete the android project and import it from existing code into Eclipse.

Actually, I need to restore some code I lost when I reverted. This even an improvement to get the database name:

databasePath = "/data/." + context.getPackageName() + "/";

Hmm...it seems to be loading to a different path than I expected. Let's see if we can find it in adb shell.

No, that's right - I had previously been making the data directory a different one from the app. So, now it's the same.

Ok, so that problem is solved. Let's commit and start our experiment.

Actually, I'll just copy the project in Eclipse. For the heck of it, I'm going to try the rename experiment again, just in case:

No, the same problem. Before I try the import, let's make sure that the package name in the manifest has to match...

Yes. Ok, let's google it.


There are some good answers on SO:

http://stackoverflow.com/questions/3697899/package-renaming-in-eclipse-android-project

I like this one:

"This is a bug in eclipse android tools http://code.google.com/p/android/issues/detail?id=2824

To Fix -> Right click on the project go to Android tools -> Rename application package..

And also check AndroidManifest.xml if it updated correctly, in my case it didn't, that should solve this problem"

Yes - it works like charm. Sweet.

Ok. So, let's make sure this works...

Ok, the licensing server isn't set up for this test yet. It's returning an error "Market Not Managed".

Uh-oh - it proceeded to download media anyway. Also, the title is the the same on both icons, some I have to change in the manifest under app name, I think.

It looks like it installed the media, despite the licence issue. I neet to fix that.

The problem is it's a callback. So, should I initiate the media download base on the callback? The validation is only called some of the time, and not on the free version. Maybe I should initiate the download from the questions activity? Or from the first time it tries to play media?

Well for now, if it's paid, I'll call it from the validation check, otherwise call it as it is now. Actually, the best thing to do would be to have something listen for a valid check, and kick off the download.

I should be able to work it through the mHandler. How does it work in this case?

Ok, how about this, when it is successful:

// will download only if audio data isn't already there
new DownloadAudioData().initiateDownload(ValidateLicense.this.mActivity);

And in the regular startActivity,

if (Utils.isFree(this)) {
// will download only if audio data isn't already there
new DownloadAudioData().initiateDownload(this);
}

There will have to be at least two versions, because for the free version, you can't include the validate code.

Ok, let's try this again.

I will have to change the string app name also.

So, there will be a few things:

Strings:

AppName
JLPT level
Nag Number

Code:

Comment out validation code
Delete validation jar from project

Manifest:

Package Name

Not trivial!

What about branching? Just a simple branch, and just keep merging in from the main trunk? I dunno. Sounds like a hassle. Let's google it.

Ok, the recommended solution seems to be to create a library project, per this post:

http://stackoverflow.com/questions/4165175/how-to-manage-multiple-editions-of-the-same-android-app

"Use an Android library project for the common code, with tiny projects for each specific flavor."

Well, let's see if this works, at least...

Ok, good. That killed it in its tracks. No media download.

I should probably go ahead and set up that common project. It will be worth it. I'll tackle it in an upcoming post.

Window leak problem - dismiss that dialog!

I was just testing my app and, after a while a couple of things cropped up. First, it looks like my percentage correct numbers are out of whack. I'll check that later doing some unit testing. But, more alarmingly, the android licensing again leaked a window on me, causing a crash. Here's the error message:


E/WindowManager( 7377): Activity com.jlptquiz.app.StartActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40640170 that was originally added here
E/WindowManager( 7377): android.view.WindowLeaked: Activity com.jlptquiz.app.StartActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40640170 that was originally added here
E/WindowManager( 7377): at android.view.ViewRoot.(ViewRoot.java:258)
E/WindowManager( 7377): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:148)
E/WindowManager( 7377): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
E/WindowManager( 7377): at android.view.Window$LocalWindowManager.addView(Window.java:424)
E/WindowManager( 7377): at android.app.Dialog.show(Dialog.java:241)
E/WindowManager( 7377): at android.app.ProgressDialog.show(ProgressDialog.java:107)
E/WindowManager( 7377): at android.app.ProgressDialog.show(ProgressDialog.java:90)
E/WindowManager( 7377): at com.jlptquiz.app.ValidateLicense.valdidateLicense(ValidateLicense.java:62)
E/WindowManager( 7377): at com.jlptquiz.app.StartActivity.validateLicense(StartActivity.java:307)

Actually, the one the other day was a bit different:

E/ActivityThread( 2074): Activity com.myapp.app.StartActivity has leaked ServiceConnection com.android.vending.licensing.LicenseChecker@405abfd0 that was originally bound here
E/ActivityThread( 2074): android.app.ServiceConnectionLeaked: Activity com.jlptquiz.app.StartActivity has leaked ServiceConnection com.android.vending.licensing.LicenseChecker@405abfd0 that was originally bound here

So, the new one looks like it's not canceling the progress dialog widget. Let's check the code...

Yeah, here it is, on the allow:

ValidateLicense.this.mProgressDialog
.cancel();

mChecker.onDestroy();

From some posts on StackOverflow, it looks like I may need to call dismiss. This might make the cancel redundant, but I'll add it in.

ValidateLicense.this.mProgressDialog.cancel();

ValidateLicense.this.mProgressDialog.dismiss();

Ok, I also changed the question activity to be consistent in how it was getting its application context, before I was sometimes getting it from a member, sometimes from the context. It may have solved the problem, because the next display was correct. I also haven't got the window problem - yet. I think I'll run it through another hundred questions to be sure.

That's a wrap!

Android package renaming - no can do!

Well, it seem I can't avoid it and longer. I'm going to start preparing my app for upload to the Google Marketplace. I've actually done this before, for a client, but this is a bigger deal. First of all, it's my app. Plus, i've put several months of work into it, as opposed to the first, which was a much simpler and less customized app. There are also some wrinkles. For example, I'm planning to upload 5 paid apps in total, all identical except for the data they use.

This url,

http://developer.android.com/guide/publishing/publishing.html

Says - "If you've followed the steps outlined in Preparing to Publish,"

Which I'll go to now...

http://developer.android.com/guide/publishing/preparing.html

Preparing to Publish: A Checklist

Publishing an application means testing it, packaging it appropriately, and making it available to users of Android-powered mobile devices.

If you plan to publish your application for installation on Android-powered devices, there are several things you need to do, to get your application ready. This document highlights the significant checkpoints for preparing your application for a successful release.

If you will publish your application on Android Market, please also see Publishing on Android Market for specific preparation requirements for your application.


// ok


Before you consider your application ready for release:

Test your application extensively on an actual device

// ok, well, I have that covered.

Consider adding an End User License Agreement in your application

// Well, it's legalese. I don't want to take the time.

Consider adding licensing support

// Ok, I've done that, at least for one version.

Specify an icon and label in the application's manifest

// Ok.

Turn off logging and debugging and clean up data/files

// ok. Already did that.

Before you do the final compile of your application:

Version your application

// Ok, I have a version and version name - 1 and 1.0.


Obtain a suitable cryptographic key

// I've got that

Register for a Maps API Key, if your application is using MapView elements

// Don't have maps

Compile your application

// Ok

After you compile your application:

// Ok, I will do that

Sign your application

// Will do already have the code

Test your compiled application

// How to do that?

Ok, one complicating factor that's already coming up is I have will 10 apps, which are simply different versions of the same app. There will be 5 paid, which I'm working on now, and five free, which I'll work on later. They just have different data. I think the easiest wat to handle this is, instead of creating copies or branches, is just rename the package, and rename strings as necessary.

The package names will be

com.kanjisoft.jlpt5.free
.
.
.
com.kanjisoft.jlpt1.free

And

com.kanjisoft.jlpt5.full
.
.
.
com.kanjisoft.jlpt1.full

In the manifest, we only have to change the package name:

package="com.kanjisoft.jlpt4.full"

The strings that have to change are:

4

10000

And in the databaseHelper:

public static String DATABASE_PATH = "/data/.com.kanjisoft.jlpt/";

I could make my life easier by reading the level to derive the database path,

something like

AppState appState = (AppState) context.getApplicationContext();

boolean isFree = Utils.isFree((Context) appState);

String freeString = null;

if (isFree){
freeString = "free";
}
else {
freeString = "full";
}

//databasePath = "/data/.com.kanjisoft.jlpt/";

databasePath = "/data/.com.kanjisoft.jlpt" + appState.jlptLevel + "/" + freeString;


Ok. But, before we test this, we need to do the name change. I'm not a whole lotta happy about messing around with the package name, but it's for the good of mankind.

Ok here's what needs to change:

First, rename the package using eclipse...

Wow, that worked.

Ok, now change the package name in manifest:

package="com.kanjisoft.jlpt4.full"

And it's getting the dreaded R error.

[2011-07-29 22:29:04 - JlptQuizApp] (skipping hidden file '/Users/markdonaghue/Documents/workspace/JlptQuizApp/res/.DS_Store')
[2011-07-29 22:29:04 - JlptQuizApp] (skipping hidden file '/Users/markdonaghue/Documents/workspace/JlptQuizApp/res/drawable/.DS_Store')
[2011-07-29 22:29:04 - JlptQuizApp] ERROR: Unable to open class file /Users/markdonaghue/Documents/workspace/JlptQuizApp/gen/com/kanjisoft/jlpt4/full/R.java: No such file or directory
[2011-07-29 22:31:00 - JlptQuizApp] R.java was removed! Recreating R.java!
[2011-07-29 22:31:00 - JlptQuizApp] R.java was removed! Recreating R.java!

Ah, the old R import somehow snuck into the source code. I think I did the shift+crtl+O trick, which generated them. Let's get rid of them...

Clean and build - still no good. The R problem can be tricky.


If I rename it to a different name in the package, then refactor it to that name, it seems to compile. Maybe that's the trick - to change int the manifest first, then refactor the package.

Ok, let's see if it runs - it has that red exclamation point next to the project. What does that mean?

It means the project has errors, and needs to be recompiled, and we're back to square one.

I might need to delete the project from eclipse, and re-import it...

And - the refactor now had an error, and doesn't change back to the original name.

Let's revert.

Ulp - that doesn't work either. It may be because the rename affects how svn manages it.

Yes, I'm trying to revert to new content. Ok, rename the project - I can always pull it from svn.

Wait, it now exists in the renamed project under the original, plus the new name.

Ok, rename it back, delete the new package...ok, we're back in business.

I'll give this another go tomorrow...

Friday, July 29, 2011

Cleaning out the closet

There is another bug I need to fix up. This one has to do with the settings when the app is newly installed. What I want to do is make sure that it default to the correct start and anding numbers on a clean install. So, what I'm going to do is clear the settings from the code. How do you do that?

How about creating a method in Utils:

public static void clearPreferences(Context context){

SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);

SharedPreferences.Editor prefEditor = sharedPreferences.edit();

prefEditor.clear();
prefEditor.commit();

}


Ok, delete the app from the device using adb shell:
$ adb shell

$su
# cd sdcard/data
# ls
com.innovativelanguage.wordpowerlite.jp
com.keramidas.TitaniumBackup
# rm ./.com.myapp.jlpt/jlpt.db
#

Clearing out the db isn't completely necessary, but srs mode uses it to access questions.

Ok, let's add the code to clear the preferences using the previous method:


InitUtils.clearPreferences(appState);


The appState object extends the application, so it's a bit redundant to access it twice, but

Ok, let's run it...

Cool. Actually, it's 1 to 10, but that's what I wanted.

Ok, I'm going to freeze the level now, as I'm selling level separately.


mLevel.setEnabled(false);

And uncomment the code to move the starting cursor back to the quiz start num:


quizStartNum.setFocusable(true);
quizStartNum.requestFocus();
quizStartNum.setSelectAllOnFocus(true);

And run it...

Ok, it's working great. Now let's try running exercisor monkey...


adb shell monkey -p com.jlptquiz.app -v 5000

Hmm...it only lasted 1500 events. What's the problem:

D/QuestionActivity( 4861): >>>>>>>>>> childCount: 4
D/PlaySoundUtil( 4861): playSound
D/PlaySoundUtil( 4861): playSoundFile start
D/UpdateSRSUtil( 4861): ========================== SRS ===========================
D/UpdateSRSUtil( 4861): Secs Remaining: 0
I/StagefrightPlayer( 68): setDataSource('/mnt/sdcard/data/com.myapp.quiz/audio_files/4_546.mp3')
D/PlaySoundUtil( 4861): playSoundFile end
D/PlaySoundUtil( 4861): playSound end
W/AudioFlinger( 68): write blocked for 159 msecs, 17 delayed writes, thread 0xea00
D/dalvikvm( 454): GC_EXPLICIT freed 39K, 49% free 2779K/5379K, external 1625K/2137K, paused 80ms
D/UpdateSRSUtil( 4861): Update execution time was 0 ms.
D/UpdateSRSUtil( 4861): Insert execution time was 2876 ms.
D/UpdateSRSUtil( 4861): Method execution time was 2883 ms.
E/MP3Extractor( 68): Unable to resync. Signalling end of stream.
D/dalvikvm( 2204): GC_EXPLICIT freed 125K, 49% free 2829K/5447K, external 1625K/2137K, paused 65ms
D/AudioHardwareQSD( 68): AudioHardware pcm playback is going to standby.
D/dalvikvm( 478): GC_EXPLICIT freed 51K, 49% free 2743K/5379K, external 1625K/2137K, paused 56ms

Unable to resync from mp3 extractor? Hmmm. What's the monkey output?

:Sending Pointer ACTION_DOWN x=126.0 y=474.0
:Sending Pointer ACTION_UP x=126.0 y=474.0
// NOT RESPONDING: com.jlptquiz.app (pid 4861)
ANR in com.jlptquiz.app (com.jlptquiz.app/.QuestionActivity)

Load: 3.15 / 2.31 / 2.27
CPU usage from 8790ms to 3689ms ago:
36% 4861/com.jlptquiz.app: 35% user + 1.3% kernel / faults: 2471 minor
24% 101/system_server: 16% user + 7.4% kernel / faults: 859 minor
21% 57/mmcqd: 0% user + 21% kernel
5.6% 4912/com.android.commands.monkey: 5% user + 0.5% kernel / faults: 124 minor
3.3% 172/com.google.android.inputmethod.latin: 2.1% user + 1.1% kernel / faults: 13 minor
1.3% 73/akmd: 0% user + 1.3% kernel
0% 68/mediaserver: 0% user + 0% kernel / faults: 51 minor 31 major
0.7% 157/com.android.systemui: 0.7% user + 0% kernel / faults: 13 minor
0.7% 4920/com.google.android.music: 0.5% user + 0.1% kernel / faults: 69 minor
0.3% 217/com.google.process.gapps: 0.3% user + 0% kernel
0.1% 2204/android.process.media: 0% user + 0% kernel / faults: 18 minor
0% 47/file-storage: 0% user + 0% kernel
0.1% 74/adbd: 0% user + 0.1% kernel
0% 197/com.android.launcher: 0% user + 0% kernel / faults: 5 minor
0.1% 2880/com.android.browser: 0% user + 0.1% kernel / faults: 1

I think this might have to do with the fact that, since I added the SRS mode, it takes longer to bring up the questions, since they are accessed in order of schedule date, over a srs joined view. A couple of options here are to get rid of the view and incorporate the schedule data directly into question. Another would be to put the access of the data into a thread using AsyncTask, but I'm not sure I want to let the user do anything until the data returns.

Well, one way to test it is to disable srs mode. Hmmm...well, let's just first run it again. Well, it made it through 5000 events this time. Let's make it 10000.

Cool, that finished with no problem. Let's up the ante to 50000.

Well, how do you like that? It made it through. Usually it crashes on an ANR or zero children on the list, which I refuse to fix because there's no chance (I think) the user will go so fast that the list doesn't have time to populate. Ok. Let's ramp it up to 100k.

And it made it! I don't know why all of a sudden it's just getting done. But, it's starting to get to the point where I'm thinking, just ship it, baby, just ship it.

Well, that's wrap for now. Hopefully, I'll be dealing with how to release issues in upcoming posts.

Aligning a textview within a scroll view

All right, I noticed a couple of things I need to clean up on this app. The first one has to do with centering aligning text from the top on the english display. For some reason, it seems to be aligning to the middle or bottom vertically, but only when there are two lines - in other words, when there's a scroll, but I think only with two lines.

You can see from the screen shot:





And that's Let's take a look the layout:


android:layout_width="fill_parent" android:layout_height="60sp"
android:layout_gravity="top|center" android:gravity="center"
android:scrollbars="vertical">
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:gravity="top|center" android:layout_gravity="top|center"
android:background="#FFEBD5A8" android:paddingLeft="10dp"
android:paddingRight="10dp" android:textSize="25sp" />


So, I have both the layout gravity at top center, and the gravity at top center. The only think I can think of to try is to make the gravity of the scroll view top|center, instead of center as it is now.

Let's give it a try:


android:layout_width="fill_parent" android:layout_height="60sp"
android:layout_gravity="top|center" android:gravity="top|center"
android:scrollbars="vertical">
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:gravity="top|center" android:layout_gravity="top|center"
android:background="#FFEBD5A8" android:paddingLeft="10dp"
android:paddingRight="10dp" android:textSize="25sp" />


Ok, problem with the licensing server...ok, I'm not connected on Panera. This game really should be allowed to be played offline, shouldn't it? Well, it is, it's only a unique case of going through a gateway. Of course, maybe I should force a check every once in a while. Sigh. There's so much you can do to lock down an app.

Rats. It's still the same.

The only thing I can try the padding:


android:layout_width="fill_parent" android:layout_height="60sp"
android:layout_gravity="top|center" android:gravity="top|center"
android:scrollbars="vertical">
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:gravity="top|center" android:layout_gravity="top|center"
android:background="#FFEBD5A8" android:paddingLeft="10dp"
android:paddingRight="10dp" android:paddingTop="1dp" android:paddingBottom="5dp" android:textSize="25sp" />


Ah, ok, I think I get it. The top|center means it will actually push the top text down the amount of its default or explicit padding, I guess. I could center it vertically which would work in the case of one and two lines, but would at least partially obscure the top line in the case of text of three lines or more.

I think the only way around it is to increase the size of the text just a little bit. I'm think maybe the scroll view affects that. Maybe if I make it, like 65sp or something, and dump the padding top and bottom:

android:layout_width="fill_parent" android:layout_height="65sp"
android:layout_gravity="top|center" android:gravity="top|center"
android:scrollbars="vertical">
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:gravity="top|center" android:layout_gravity="top|center"
android:background="#FFEBD5A8" android:paddingLeft="10dp"
android:paddingRight="10dp" android:textSize="25sp" />



Ah, I get it. It's not scrolling any more, which means not matter how much bigger I set the scroll_view layout height, because the textview layout_height is wrapping the content, it's not going to get bigger. In other words, the wrapped textview is now smaller than the (increased) layout height. It doesn't look that much different, but now we're working with default Android values, so I'm willing to settle for it. That's a wrap for now. The new image is below. Can you see the difference? It's an improvement :)

Google licensing - the fallout

Did I say I was done with the implementation of Google licensing yesterday? Not quite, it turns out. I had doubts about just checking once, so I put it in to check every time. The problem of course is if you're off the network. First of all, I can check to see if the network is connected, and if not, check a flag. But, working from Panera, it turns I was connected, but Panera's gateway wasn't forwarding my packets, probably because I hadn't clicked "ok" on the login screen. So, this might be a problem for people saving money on phone charges by using free internet at coffee shops.

There's also an issue if you hit "back" on the dialog the tells you you're unlicensed - it takes them right into the program! You need to disable the cancel on the dialog.

Also, just random testing has this one crop up.

E/ActivityThread( 2074): Activity com.myapp.app.StartActivity has leaked ServiceConnection com.android.vending.licensing.LicenseChecker@405abfd0 that was originally bound here
E/ActivityThread( 2074): android.app.ServiceConnectionLeaked: Activity com.jlptquiz.app.StartActivity has leaked ServiceConnection com.android.vending.licensing.LicenseChecker@405abfd0 that was originally bound here


Ok, this could be the problem:

http://developer.android.com/guide/publishing/licensing.html

Failing to call the LicenseChecker's onDestroy() method can lead to problems over the lifecycle of your application. For example, if the user changes screen orientation while a license check is active, the application Context is destroyed. If your application does not properly close the LicenseChecker's IPC connection, your application will crash when the response is received. Similarly, if the user exits your application while a license check is in progress, your application will crash when the response is received, unless it has properly called the LicenseChecker's onDestroy() method to disconnect from the service.

Here's an example from the sample application included in the LVL, where mChecker is the LicenseChecker instance:

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


Ok, will, since I'm never leaving the calling activity, it's not getting destroyed. So, I should just call it after the validation is complete.

I'll just at this:

mChecker.onDestroy();


to the allow, dontAllow and applicationError methods of my validating class.

Let's give that another try.

Well, it seems to be working ok. Ok, that's a wrap for now!

Thursday, July 28, 2011

Android Licensing - finished, at least.




Well, now that I've got the Google copy protection completed, the next step is what to do. It's easy enough if it gets accepted - just proceed as normal. If it comes through as non-licensed or other error, though, I guess the idea is going to be to basically push out a message and exit. Simple. Hopefully this will be a nice, short post.

Ok, so the server is set up as "NOT Licensed" for testing.

Here is what the dialog code will look like,


// 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.

public void dontAllow() {
if (isFinishing()) {

// Don't update UI if Activity is finishing.
return;
}

// displayResult(getString(R.string.dont_allow));
displayLicensingCallbacResult("dont allow");

AlertDialog.Builder alertbox = new AlertDialog.Builder(StartActivity.this);

// set the message to display
alertbox.setMessage("The Google licensing server is unable to validate this copy of the application. Please contact support@kanjisoft.com");

// add a neutral button to the alert box and assign a click listener
alertbox.setNeutralButton("Ok", new DialogInterface.OnClickListener() {

// click listener on the alert box
public void onClick(DialogInterface arg0, int arg1) {
// the button was clicked
Toast.makeText(getApplicationContext(), "OK button clicked", Toast.LENGTH_LONG).show();
finish();
}
});

// show it
alertbox.show();

}

@Override
public void applicationError(ApplicationErrorCode errorCode) {
// TODO Auto-generated method stub

Log.d(StartActivity.TAG, "App error, code: " + errorCode);
Log.d(StartActivity.TAG, ">>>>>>>>>>>>> don't allow!");
// displayResult(getString(R.string.dont_allow));
displayLicensingCallbacResult("dont allow");


}

private void displayLicensingCallbacResult(final String result) {
mHandler.post(new Runnable() {
public void run() {
Toast.makeText(StartActivity.this, result,
Toast.LENGTH_LONG).show();

AlertDialog.Builder alertbox = new AlertDialog.Builder(StartActivity.this);

// set the message to display
alertbox.setMessage("The Google licensing server is unable to validate this copy of the application. Please retry or contact support@kanjisoft.com");

// add a neutral button to the alert box and assign a click listener
alertbox.setNeutralButton("Ok", new DialogInterface.OnClickListener() {

// click listener on the alert box
public void onClick(DialogInterface arg0, int arg1) {
// the button was clicked
Toast.makeText(getApplicationContext(), "OK button clicked", Toast.LENGTH_LONG).show();
finish();
}
});

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

}


Ok, here's the problem - the user can go ahead and start playing without regardless of the results of the check. Is it because this display is in a callback routine? If I let it set for a while, then the disallowed message pops up. But, before that happens, if they hit start and get into the question display, the invalid license dialog box doesn't show up until they get back to it.

That's good in a way, since it doesn't leave the valid customers hanging around waiting for the clearance. But why doesn't the dialog box pop up once it gets into the question display?


It might have something to do with this:

private void displayLicensingCallbackResult(final String result) {
mHandler.post(new Runnable() {
public void run() {
Toast.makeText(StartActivity.this, result,
Toast.LENGTH_LONG).show();


Android might not be able to see the mHandler, a member of StartActivity, until it comes back to the StartActivity display. Hmm...I could make the same call at the start of the question activity...then I would exit the whole app. Just make it part of the initialization routine. Is there a way to exit the whole app, like a system.exit?

Well, I guess system.exit isn't recommended. Per this thread:

http://stackoverflow.com/questions/2042222/android-close-application

It looks like the best way is just to call:

moveTaskToBack(true)

Ok, let's see if we can move this validation code to the initalize method.


Better yet, create an object for handling it so it will be nicely encapsulated functionally:


I can even make it an activity. But, how will that work if there's not corresponding display? And do I really want to make a display for this? Let's experiment and see what happens when we don't do a display.

Oh, yeah, can't forget to declare it in the manifest.

Ok, well, it's kind of dark. I could set up a background for it. I don't think there's any way I put it in the question display, I don't want that network lookup lag.

Hmm...even when I test it with the server tet to accept, it's rejecting it.

I'm starting to think it might be a good idea to have a global setting that's checked every time a question is asked. Yeah, that way, it won't slow things down, and yet it will be checked every time.

Ok, it's still showing up as unlicensed. Why, it worked fine yesterday. Does it matter what account I'm logged into on my phone?

Let me change the server to an app error message. I have a feeling something is getting cached somewhere. It's a real pain.

I'm logged onto the wireless on my phone..rather I *was*. Oh, yeah, I rebooted to try to clear the cache. Reconnecting. Rats. Same problem - same response, invalid license.

Here's the log...

/dalvikvm( 216): GC_CONCURRENT freed 889K, 49% free 3540K/6855K, external 1625K/2137K, paused 2ms+5ms
D/dalvikvm( 216): GC_CONCURRENT freed 707K, 49% free 3544K/6855K, external 1625K/2137K, paused 2ms+3ms
I/LicenseChecker( 941): Received response.
I/LicenseChecker( 941): Clearing timeout.
W/LicenseValidator( 941): An error has occurred on the licensing server.

Hmm? Ok, that's correct. That's the response I have set up. Well, I dunno. I might not have saved it - what was it before? Change it back to allowed. Ah, ok. There we go.

Ok, I think I need to make this an AsyncThread.

Actually, this should be a one-time check only. Once it works, it works. Set a flag and be done with it. Maybe show a message like "validating the license, please wait a few moment".




Ok, now it's working ok. Now, I just need to add to to either app state or just save it in local settings.

Ok, so, I've moved the validation call into the init function which is called in every activity. But, even before I call that, I'll just check the validated flag, which is stored in settings.

Ok, I may have had an error in my code earlier, and was displaying the unlicensed message even on the invalid server message.

Ok, now, it seems to be working ok in the unlicensed case. Let's try the licensed case...

Ok, it doesn't like calling an activity from outside an activity:

E/AndroidRuntime( 1918): java.lang.RuntimeException: Unable to resume activity {com.jlptquiz.app/com.jlptquiz.app.QuestionActivity}: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

Well, I'm not in love with the idea. But what are the options? I want to call it from everywhere, and initialize is called from everywhere, but it's static. What's the harm? But what's this new task? I don't think I want a new task.

public static final int FLAG_ACTIVITY_NEW_TASK

Since: API Level 1
If set, this activity will become the start of a new task on this history stack. A task (from the activity that started it to the next task activity) defines an atomic group of activities that the user can move to. Tasks can be moved to the foreground and background; all of the activities inside of a particular task always remain in the same order. See Tasks and Back Stack for more information about tasks.
This flag is generally used by activities that want to present a "launcher" style behavior: they give the user a list of separate things that can be done, which otherwise run completely independently of the activity launching them.
When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.
This flag can not be used when the caller is requesting a result from the activity being launched.
Constant Value: 268435456 (0x10000000)


Well, it looks like a menu kind of thing. Ok, what we can do for now is pull it out of initialization, back into the StartActivity. Then, just have QuestionActivity check the flag and finish if it's not licensed. That might do the trick.

Ok, then in QuestionActivity, we'll just check the flag:

if (!mAppState.licensced){

// will be checked in start activity
finish();
}

Ok, let's run this one. Good, seems to work ok. Now, I need to clear the valid license flag in my code. then set the server setting to unlicensed, and see what happens.

The question is, how to clear that field? It's a little tricky, I only want to clear it once. Maybe I'll use a boolean.

if (this.firstTimeTest){
this.firstTimeTest = false;
appState.licensed = false;
InitUtils.saveState(this);
}


Boy, how I would like to refactor into appState.save(); I'll get to it.

Ok, let's make sure this works first.

Ok, I saved the server to unlicensed. Now, let's run the test.

Good, looks good. Instead of the black screen, I'd like to give it a nicer looking background. I'll just use the start activity layout and take out all the data fields.

Ok, pretty good. I have my too-common dropped word in the message, need to fix that. Actually, I think the background might have been the start screen, I just didn't realize it, because it's kind of dark.

Ok, I'm liking this. Let's try it with a different error message. like ERROR_SERVER_FAILURE.

Wait. When I restart the app, it doesn't redo the check - it just sit's on the beginning display. Ah, It needs to be in onResume.

No, it is in onResume.

Well, there's something strange going on, like there are two displays...oh, yeah, I need to change the name of the layout in the check verification source code. Although I could leave it the same. I think the problem is that when I move it to back, it's coming back into the verification display, which doesn't do the check. Let's try this:

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


Where I add the finish, and see if it comes back into the start activity instead of the validation activity.

No, it's not quite working. What I should do is check the app status on return from that activity, and then just call finish.

How to do that? Ok here it is from SO:

Intent i = new Intent(this,CameraActivity.class);
startActivityForResult(i, STATIC_INTEGER_VALUE);


And this:

resultIntent = new Intent(null);
resultIntent.putExtra(PUBLIC_STATIC_STRING_IDENTIFIER, tabIndexValue);
setResult(Activity.RESULT_OK, resultIntent);
finish();


And this:

Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case (STATIC_INTEGER_VALUE) : {
if (resultCode == Activity.RESULT_OK) {
int tabIndex = data.getIntExtra(PUBLIC_STATIC_STRING_IDENTIFIER);
// TODO Switch tabs using the index.
}
break;
}
}
}


Whew. Who would've thought a simple validation check could get so complicated? Why can't I just display a dialog?

I can just set a context in the licence checking class.

Hmm...when I try to do that, this:

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {

has a problem with the "isFinishing", which is part of the Activity usually I guess. I guess I should pass the activity as an Activity instead of a Context.

Ok, we'll try it like this:

private void validateLicense() {
new ValidateLicense().valdidateLicense(this);
}

Ok, so, let's try this now.

Ah, yes. Much better. It's showing the same dialog, but this time against the real StartActivity layout. And it may not be necessary to show the "please wait", because it seems to go pretty quickly.

It seems a bit less safe to rely on a setting for the application. Maybe I should do it every time the app is restarted?

No, it's way to long. Also, I should put the validating message as a toast.long.

Well, now it's quick, even when I use task killer on it. Let's restart the phone.

It's a bit confusing now. It displayed the message quickly, I think, I when I first started it, but then the display went semi-dark and the app wouldn't react at all. Ok, I just did a number of restarts, and sometimes it's pretty slow. So, I'm only going to do it the once, when they first install the app.

This could really use a progress display - just that little circle....

Here we go, from http://developer.android.com/guide/topics/ui/dialogs.html#ProgressDialog

ProgressDialog dialog = ProgressDialog.show(MyActivity.this, "",
"Loading. Please wait...", true);

How about this:

ProgressDialog dialog = ProgressDialog.show(activity, "",
"Validating license, please wait a few moments...", true);

checkLicense();

dialog.cancel();


The cancel is no good due to the callback, which is async and therefore lets it fall through the cancel immediately. So, this works:

ProgressDialog dialog = ProgressDialog.show(activity, "",
"Validating license, please wait a few moments...", true);

checkLicense();

And we get a nice spinning wheel. Let's see if I can grab a picture of it.




Also shown at the beginning.

Ok, let's double-check it's working when the server returns a valid code...

Uhf. I accidentally hit something like back, and it seems to have gone into a loop displaying that progress circle...

Hmmm...I really need to figure out a way to cancel it.

It keeps getting this:

/vending ( 1124): [12] RequestRunnable.run(): Got ApiException from async request: HTTP 503 for http://android.clients.google.com/market/licensing/LicenseRequest
E/vending ( 1124): [12] RequestRunnable.run(): Got ApiException from async request: HTTP 503 for http://android.clients.google.com/market/licensing/LicenseRequest
E/vending ( 1124): [12] RequestRunnable.run(): Got ApiException from async request: HTTP 503 for http://android.clients.google.com/market/licensing/LicenseRequest
E/vending ( 1124): [12] RequestRunnable.run(): Got ApiException from async request: HTTP 503 for http://android.clients.google.com/market/licensing/LicenseRequest
D/dalvikvm( 1124): GC_CONCURRENT freed 520K, 52% free 3036K/6279K, external 1625K/2137K, paused 6ms+3ms
E/vending ( 1124): [12] RequestRunnable.run(): Got ApiException from async request: HTTP 503 for http://android.clients.google.com/market/li


Well, this could be the problem:

http://developer.android.com/guide/publishing/licensing.html

The licensing server applies general request limits to guard against overuse of resources that could result in denial of service. When an application exceeds the request limit, the licensing server returns a 503 response, which gets passed through to your application as a general server error. This means that no license response will be available to the user until the limit is reset, which can affect the user for an indefinite period.

What I don't get is why the 503 isn't passed through as some kind of an error. In fact, not of the codes except licensed return anything, except a call to dontAllow, which doesn't show any error codes.

Actually, the problem is, or part of the problem is that when the app is successufully verified, and the validation object goes out of scope, the progress indicator stays on, probably because it maybe runs in separate thread. The two ways around are 1) to cancel it on return or 2) set up a dialog box similar to the one for the denied result. I'll go for the second.

Ok, for some reason the dialog has to be called in this code:

private void displayLicensingCallbackResult(final String result) {
mHandler.post(new Runnable() {
public void run() {

AlertDialog.Builder alertbox = new AlertDialog.Builder(
ValidateLicense.this.mActivity);


etc.

Why does the handler have to be used here?

Alright. Let's give it a try. It's on success right now.

Oh, it's exiting the app. Since all the result displays are going through this:

private void displayLicensingCallbackResult(final String result) {

So, we need to check for not success:

if (!SUCCESS.equals(result)) {
ValidateLicense.this.mActivity.finish();
}

I'm not to keen on the not, but it's ok here, I suppose.

Ok, let's try again.

Nope, still not cancelled. I'll have to specifically do it.

Ok. Let's try this:

ValidateLicense.this.mProgressDialog.cancel();

if (SUCCESS.equals(result)) {
// noop
} else {
ValidateLicense.this.mActivity.finish();
}


Now let's double check the negative logic again, set the server to a negative response.

Cool, it came up with the error code. Pretty fast, too.

Ok, the success works fine. What happens when not connected?


I'll put in airplane mode and restart. I think it might cache the result or something.

Actually, it got a few of these:

/vending ( 2319): [13] RequestDispatcher.performRequestsOverNetwork(): IOException while performing API request: Connection to http://android.clients.google.com refused
E/vending ( 2319): [13] RequestRunnable.run(): Got IOException from async request: Connection to http://android.clients.google.com refused
W/vending ( 2319): [13] RequestDispatcher.performRequestsOverNetwork(): IOException while performing API request: Connection to http://an

It's too bad it doesn't give a more specific error on a timeout.

Ok. I think I've covered the basics here. I may improve on it later on, like sending it to the market or something. But for now, that's a wrap!

Wednesday, July 27, 2011

Android Copy Protection - completed!

Ok, I have decided to get this thing off the ground and launch. I know it's the worst possible strategy - just upload it to the marketplace and hope for the best. But, time is of the essence and I want to get going on the IOS version.

Some issues: I ran exerciser money and the dreaded music player error is back. I don't now how my last change, adding a browse mode could have affected it. But I'm no longer seeing the "music player not found" message, which means it's somehow finding it and playing the mp3s on a separate track.

I'm also geting an NPE on on a 0 zero children found on the listview. However, debug statements show it's getting filled properly, so it seems to be problem with just generating events so quickly. I could fix it by adding a time to wait if children aren't found, but I don't think it's worth it.

But, the the main issue at hand seems is successfully testing the copy protection code, or licensing. I already gave this a go once, and *almost* got it done, see this post http://gettingintomobile.blogspot.com/2011/07/android-licensing-completion.html and a couple before it.

I guess I had actually uploaded it, but unpublished. Let's take a look at the code:

So, the basic idea is I can change the profile to don't allow, and want to see it show up on the log, which I was unable to do. First, let's see if I can figure out what my vendor account was.

Ok, going back and reviewing this post:

http://gettingintomobile.blogspot.com/2011/07/securing-app-part-3-google-licensing.html

It says:

When a response is received, LicenseChecker creates a LicenseValidator that verifies the signed license data and extracts the fields of the response, then passes them to your Policy for further evaluation.

// ok

If the license is valid, the Policy caches the response in SharedPreferences and notifies the validator, which then calls the allow() method on the LicenseCheckerCallback object.

Ok, so, the problem I was having was that it was "allowing", although I had set it to not allow.

This is apparently why:

http://stackoverflow.com/questions/3497280/android-not-market-managed-error

"Weird. After uploading the application (just uploading and saving draft, not filling any other required fields), the "Market Licensing Example" stops issuing the NOT_MARKET_MANAGED error, despite the fact that clearly selected NOT_LICENSED. Instead it responds with the message Allow the user access. I also tried setting (for test & learning purposes) it to ERROR_SERVER_FAILURE but I get the same Allow the user access message."

Well, let's see if I can log in to the server:

"And when you log into http://market.android.com/publish/editProfile you will see an option for changing the License Test Response:"


http://market.android.com/publish/editProfile

Ah, there it is. Ok, let me add that account to my phone - I wiped it when I rooted the phone.

mycompany@gmail.com


Ok, there's the app I uploaded a while ago, early July. Now, let's see, Ok, in "edit profile", the response is "not licenced".

Ok, so - I made sure my phone's connected. It seemed as if it couldn't connect last time and defaulted to accept. Let's run the app.
It should say "don't allow"...

/QuizStartActiity( 2128): >>>>>>>>>>>>> don't allow!
W

Yes! Sweet. Ok, I wonder if I can test the allow again by changing the status on the server...

D/QuizStartActiity( 2189): >>>>>>>>>>>>> allow!


Sweeter than sweet. Here is a look at the code:

private Handler mHandler;
private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;


private BroadcastReceiver receiver;
// Generate your own 20 random bytes, and put them here.
private static final byte[] SALT = new byte[] { (20 2 digit pos. or neg. integers, separated by commas };

private static final String BASE64_PUBLIC_KEY = // fill in from your developer account

private void checkLicense() {

Log.d(TAG, "checkLicense start");

// for licensing code
mHandler = new Handler();

// Try to use more data here. ANDROID_ID is a single point of attack.
String deviceId = Secure.getString(getContentResolver(),
Secure.ANDROID_ID);

// 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);

doCheck();

Log.d(TAG, "checkLicense end");

}

private void doCheck() {
mChecker.checkAccess(mLicenseCheckerCallback);
}


public synchronized void checkAccess(LicenseCheckerCallback callback) {
// If we have a valid recent LICENSED response, we can skip asking
// Market.
if (mPolicy.allowAccess()) {
Log.i(TAG, "Using cached license response");
callback.allow();
} else {
LicenseValidator validator = new LicenseValidator(mPolicy, new NullDeviceLimiter(),
callback, generateNonce(), mPackageName, mVersionCode);

if (mService == null) {
Log.i(TAG, "Binding to licensing service.");
try {
boolean bindResult = mContext.bindService(
new Intent(ILicensingService.class.getName()),
this, // ServiceConnection.
Context.BIND_AUTO_CREATE);

if (bindResult) {
mPendingChecks.offer(validator);
} else {
Log.e(TAG, "Could not bind to service.");
handleServiceConnectionError(validator);
}
} catch (SecurityException e) {
callback.applicationError(ApplicationErrorCode.MISSING_PERMISSION);
}
} else {
mPendingChecks.offer(validator);
runChecks();
}
}
}

private void runChecks() {
LicenseValidator validator;
while ((validator = mPendingChecks.poll()) != null) {
try {
Log.i(TAG, "Calling checkLicense on service for " + validator.getPackageName());
mService.checkLicense(
validator.getNonce(), validator.getPackageName(),
new ResultListener(validator));
mChecksInProgress.add(validator);
} catch (RemoteException e) {
Log.w(TAG, "RemoteException in checkLicense call.", e);
handleServiceConnectionError(validator);
}
}
}

private synchronized void finishCheck(LicenseValidator validator) {
mChecksInProgress.remove(validator);
if (mChecksInProgress.isEmpty()) {
cleanupService();
}
}


That's a wrap for now. The next step is to add some dialog indicating there's a problem with the server, contact me etc.

Sunday, July 24, 2011

Amazon App Store vs. Google Marketplace - research

I'm getting close to the point of releasing this beast I've been working on for the better part of four months. This get into an area I am very interested in - product management. It's basically a full time job in itself, but the payoff is in sales. One of strategies I've seen discussed is releasing an app into the Amazon App Store and basically giving it away for a day for free. This hopefully garners some attention and gets some eyeballs on the app, and maybe some buzz going on. Although, frankly the nature of this app is "long-tail", a Japanese vocabulary builder and study app oriented toward the Japanese Langauge Proficiency test. But, it would really nice to get a splash of publicity. I just don't know if it's the type of thing that really garners publicity. It's nichy, but it's crowded niche - especially on the IOS side, but that can wait for another day.

The first hit on Google brings us to the leading app store page:

http://www.amazon.com/mobile-apps/b?ie=UTF8&node=2350149011

The main part of the page start out with this heading:

Amazon Appstore for Android

Get a great paid app for free every day <=== Right, I should be looking at this.

They have an "App of the Day" which is offered for free, presumably just for today, with $1.99 regular price crossed out. There's also a clock underneath which shows how much time is left before the giveaway runs out.

Underneath that is a headline that says "Featured Categories and Developers". The categories are PopCap games, whatever that is, Games, "Citizen 12 Games" (a developer?), Music, Entertainment, and Productivity.

Then came a heading called "Most Recent Free Apps of the day", then, "Popular Apps for Android". then "Popular Games for Android" then "Great Games from HandyGames for Android", then "Bestselling Android Phones and Tablets".

The game apps seem to be 1 or 2 bucks, while two of the four productivity softwares headlined are as high $7 and $8 bucks. This goes to show that if theres's money involved, people will pay more. I don't know if that applies to educational apps, though.

Then, on the left side, there's a whole list of categories:

Browse
Popular Features

Bestsellers
Top Rated Apps
New Releases <====== interesting
Free Apps
Deals <====== interesting
Amazon Apps

Test Drive

Most Engaging
All Test Drive Apps

Hot Links

Android 101
Android Phones
Android Tablets

Categories

Books & Comics
City Info
Communication
Cooking
Education <====== my app's category
Entertainment
Finance
Games

Action

Adventure

Arcade

Board

Cards

Casino

Casual

Educational <==== more of my apps category

Kids

Multiplayer

Music

Puzzles & Trivia

Racing

Role Playing

Sports

Strategy

Health & Fitness
Kids
Lifestyle
Magazines
Music
Navigation
News & Weather
Novelty
Photography
Podcasts
Productivity
Real Estate
Reference
Ringtones
Shopping
Social Networking
Sports
Themes
Travel
Utilities
Web Browsers

Featured Developers

Adobe
Evernote
Gameloft
GAMEVIL
Glu Mobile
Handmark
HeroCraft
Namco Bandai
PopCap
Real Networks
Rovio


Ok, a quick question - what are Deals? I will check that out after checking out the right site of the pages, which contains the following:

Educational Fun For Kids

Teach your toddler words by association: zebras, toucans, fire trucks, and more with Sound Touch. Hundreds of colorful real life images are included--simply touch one to hear its accompanying sound. $1.99 (regularly $2.99)


At least 50% off Premium Apps

OfficeSuite Pro
Keep a feature-rich mobile office solution by your side with OfficeSuite Pro. Create, view, and edit MS Word and Excel files -- this app for Android also supports viewing of PowerPoint and PDF files. Read more
$4.99 (regularly $14.99)

DioPen Handwriting & Keyboard: $4.99 (regularly $10.40)
Droid Scan Pro: $1.99 (regularly $4.99)
Cardio Trainer Pro: $4.99 (regularly $9.99)
Gaia GPS - Topos and tracking: $4.99 (regularly $9.99)
›See more

Ok. So, that's a thing - it will list deals. But clicking on the link reveals that it's only listing 10 apps altogether. Perhaps the "deals" link on the left side will have more? Nope, it takes you to the same page. That's disappointing.

Anyway, continuing down the right side, we have another block add for a game, then underneath that, the following link:

Bestsellers
Appstore for Android : Amazon Appstore
Updated hourly


And then two tabs, "Top 100 Paid", and "Top 100 Free", and then icons, titles and prices of the top 10 apps in each category. Underneath that, we have this link:

›See all bestsellers in Amazon Appstore

And then underneath it all an Amazon-ish "recently viewed" including apps and books.

Ok, so, clearly a lot of thought has gone into this setup. Of course, I'm viewing from my macbook, and it's a web page, not the actual app store app. But they seem to allow you to download from it - it must get used quite a bit.

I should also note that the apps that are displayed also show a rating and number of reviews, and if you drill down you get to look at the reviews, which are just like the regular Amazon book reviews.

Oh, yeah, there are "test-drive apps" which I just figured out mean you can try the app on your computer. How does that work? Ok, here we go:

Clicking the “Test drive now” button launches a copy of this app on Amazon Elastic Compute Cloud (EC2), a web service that provides on-demand compute capacity in the cloud for developers. When you interact with the simulated phone using your mouse and keyboard, we send those inputs over the Internet to the app running on Amazon EC2 — just like your mobile device would send a finger tap to the app. Our servers then send the video and audio output from the app back to your computer. All this happens in real time, allowing you to explore the features of the app as if it were running on your mobile device.

That's just cool. The customer can actually try out the app, and if they like it, they can buy it.


Ok, let's drill into education, then "Japanese". There are 3 pages altogether. Not that huge. A lot of the apps are free, but some are paid. Uh-oh. I just saw one review that said it didn't work on their phone. That's one thing I haven't done - tested on other phones. That's why it would be cool to distribute the free one first, work out the bugs with less pressure, and then come out with the paid version.

I just realized that if you want to do an in-app upgrade, you'll need different code for Amazon or Google's App marketplace. Just another thing to keep track of.

I think a good beta-testing option would be to just release the app with a limited number of words available, like 100. That will keep the reviews from getting too nasty of they don't work. The problem is that you can't put adds in until you have released thea app on the google marketplace. Actually, I think I read somewhere that there's a way to add an app to the Google Marketplace "secretly", or something. Let me check into that.

Here we go, from Google Marketplace's upload instructions:

APK file size: Maximum supported size is 50MB.
Draft application .apk file: When you upload an .apk file, it can be saved as a draft while you edit the other aspects of the list.
Please note that package names for app files are unique and permanent, so please choose wisely. Package names cannot be deleted or re-used in the future.

Important note;

Copy protection: Copy protection may help prevent applications from being copied from a device. It increases the amount of memory on the phone required to install the application. We expect to deprecate copy protection soon.
If you have a paid application, we recommend that you implement the Android Market licensing service. For free application, we recommend implementing your own copy protection scheme.


Ok, the question is, if I upload it as an app, will I have a site to give to Jumptap?

Hmmm. I just had a good idea. What if I charge by 100 words? Like, you pay a buck for 100 words? That way, you get the free app, with the first 100 words. Then, you get the paid app for 99 cents for 200 words, and so on.

It's so annoying that you have to have separate free and paid versions on Google's app marketplace. But, the free app could be without the SRS. The problem is when they download the paid app, it will have to use a separate database, and they lose the SRS. Plus, it's a bit of a different demo. If I could give SRS with the free version it would be a good user test of how it works.

Hmmm..

Anyway, let's take a look at the Google Marketplace on the web. I've seen the Marketplace app before. But the web page will give an idea of what they're up to. I saw they recently revamped it.

Here's what's in the web page:

https://www.google.com/enterprise/marketplace/?pli=1

First, a link to a video:

Video: Google Apps Marketplace - 1 year of integrated apps Youtube video

Underneath that, a box called "Featured Apps", which is like a slide viewer, and features 5 apps in total, with at least one free one.

Then, there's a section entitled "Try popular & notable apps", which list five apps, with fairly large icons and descriptive text. Underneath that, a section labeled "Success stories from customers like you".

On the left side, similar to Amazon's marketplace, is a list of categories:

Products

Accounting & Finance
Admin Tools
Calendar & Scheduling
Customer Management
Document Management
EDU
Productivity
Project Management
Sales & Marketing
Security & Compliance
Workflow

Professional Services

Archiving & Discovery Implementation
Custom Application Development
EDU Specialists
Google Analytics
Medium-Large Business Implementation
Small Business Implementation
Support & Managed Services
Training & Change Management

Enterprise Search
Products

Content Connectors
OneBox Modules
Search Extensions

Professional Services

GSA Deployment
Google Mini Deployment
Custom Development
Training
GeoSpatial Solutions



And on the right side, "Top Installed this week", with the number of reviews, then "Top Installed", presumably overall, then a "recently added" section.

Again, this is the web page. The marketplace app looks completely different, at least the version I have on my phone. It has a few icons, but I mostly just search on keywords with it, then drill down and check the reviews.

I just noticed while browsing another android app market called "Android Market" at https://market.android.com/. It has a similar slideshow to Google's of featured apps, which looks to feature about 5 apps or so. Underneath that are tabs for "top charts", including "top paid", "top free" (Google Maps, Facebook, Pandora, Angry Birds, YouTube), Top Grossing (looks like all games), Top new paid, and top new free. In the center, under a "Featured" tab. There's one that catches my eye, called "Perfect ear" (I play guitar, well, "play" is a bit of a stretch, but anyway). It has 200+ reviews, not bad, and goes for $1.99. So, he made at least $400 on it. If 1/10 of the users reviewed it, that's 4k. I wonder what the typical purchase/review is?

So, the numbers are pretty good on this site. The thing that kind of concerns me is keeping all these sites with up-to-date versions and fixes. Also, specific code for the various marketplaces. It's a lot to contend with.

Where else are Android apps available?

Here's another one, appolicious, at http://www.androidapps.com/. It also has IOS tab. It looks really pro.

Here's another one:

http://www.androidzoom.com/

And then into review sites. Ok. That's an overview. I'm still trying to work out the best release strategy. More to come/