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.

No comments:

Post a Comment