Market licensing server library core.
new file: ILicenseResultListener.aidl
new file: ILicensingService.aidl
new file: LicenseChecker.java
new file: LicenseCheckerCallback.java
new file: Policy.java
new file: ResponseData.java
new file: StrictPolicy.java
new file: LicenseValidator.java
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/ILicenseResultListener.aidl b/samples/MarketLicensing/src/com/android/vending/licensing/ILicenseResultListener.aidl
new file mode 100755
index 0000000..869cb16
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/ILicenseResultListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+oneway interface ILicenseResultListener {
+ void verifyLicense(int responseCode, String signedData, String signature);
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/ILicensingService.aidl b/samples/MarketLicensing/src/com/android/vending/licensing/ILicensingService.aidl
new file mode 100755
index 0000000..9541a20
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/ILicensingService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+import com.android.vending.licensing.ILicenseResultListener;
+
+oneway interface ILicensingService {
+ void checkLicense(long nonce, String packageName, in ILicenseResultListener listener);
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/LicenseChecker.java b/samples/MarketLicensing/src/com/android/vending/licensing/LicenseChecker.java
new file mode 100755
index 0000000..773b8cd
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/LicenseChecker.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+import java.security.SecureRandom;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.vending.licensing.LicenseCheckerCallback.ApplicationErrorCode;
+import com.android.vending.licensing.Policy.LicenseResponse;
+
+/**
+ * Client library for Android Market license verifications.
+ *
+ * The LicenseChecker is configured via a {@link Policy} which contains the
+ * logic to determine whether a user should have access to the application.
+ * For example, the Policy can define a threshold for allowable number of
+ * server or client failures before the library reports the user as not having
+ * access.
+ *
+ * This library is not thread-safe. Multiple, concurrent checks will result in
+ * an error.
+ */
+public class LicenseChecker implements ServiceConnection {
+ private static final String TAG = "LicenseChecker";
+
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ private ILicensingService mService;
+
+ /** Validator for the request in progress. */
+ private LicenseValidator mValidator;
+
+ private final Context mContext;
+ private final Policy mPolicy;
+ /** Listener for service (IPC) calls. */
+ private final ResultListener mListener;
+ private final String mPackageName;
+ private final String mVersionCode;
+
+ public LicenseChecker(Context context, Policy policy) {
+ mContext = context;
+ mPolicy = policy;
+ mListener = new ResultListener();
+ mPackageName = mContext.getPackageName();
+ mVersionCode = getVersionCode(context, mPackageName);
+ }
+
+ private boolean isInProgress() {
+ return mValidator != null;
+ }
+
+ /**
+ * Checks if the user should have access to the app.
+ *
+ * @param callback
+ */
+ public synchronized void checkAccess(LicenseCheckerCallback callback) {
+ if (isInProgress()) {
+ callback.applicationError(ApplicationErrorCode.CHECK_IN_PROGRESS);
+ }
+
+ mValidator = new LicenseValidator(mPolicy, callback, generateNonce(), mPackageName,
+ mVersionCode);
+
+ Log.i(TAG, "Binding to licensing service.");
+ boolean bindResult = mContext.bindService(new Intent(ILicensingService.class.getName()),
+ this, // ServiceConnection.
+ Context.BIND_AUTO_CREATE);
+
+ if (!bindResult) {
+ Log.e(TAG, "Could not bind to service.");
+ callback.dontAllow();
+ // No need to unbind at this point.
+ return;
+ }
+ }
+
+ private class ResultListener extends ILicenseResultListener.Stub {
+ public void verifyLicense(int responseCode, String signedData, String signature) {
+ mValidator.verify(responseCode, signedData, signature);
+ cleanup();
+ }
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ILicensingService.Stub.asInterface(service);
+
+ try {
+ Log.i(TAG, "Calling checkLicense on service for " + mValidator.getPackageName());
+ mService.checkLicense(mValidator.getNonce(), mValidator.getPackageName(), mListener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException in checkLicense call.", e);
+ handleServiceConnectionError();
+ // cleanup unbinds service.
+ cleanup();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ // Called when the connection with the service has been
+ // unexpectedly disconnected. That is, Market crashed.
+ Log.w(TAG, "Service unexpectedly disconnected.");
+ handleServiceConnectionError();
+ // cleanup unbinds service.
+ cleanup();
+ }
+
+ private void handleServiceConnectionError() {
+ if (mPolicy.allowAccess(LicenseResponse.CLIENT_RETRY)) {
+ mValidator.getCallback().allow();
+ } else {
+ mValidator.getCallback().dontAllow();
+ }
+ }
+
+ /** Resets request state. */
+ private synchronized void cleanup() {
+ mContext.unbindService(this);
+ mValidator = null;
+ }
+
+ /** Generates a nonce (number used once). */
+ private int generateNonce() {
+ return RANDOM.nextInt();
+ }
+
+ /**
+ * Get version code for the application package name.
+ *
+ * @param context
+ * @param packageName application package name
+ * @return the version code or empty string if package not found
+ */
+ private static String getVersionCode(Context context, String packageName) {
+ try {
+ return String.valueOf(context.getPackageManager().getPackageInfo(packageName, 0).
+ versionCode);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Package not found. could not get version code.");
+ return "";
+ }
+ }
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/LicenseCheckerCallback.java b/samples/MarketLicensing/src/com/android/vending/licensing/LicenseCheckerCallback.java
new file mode 100755
index 0000000..1567497
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/LicenseCheckerCallback.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+/**
+ * Callback for the license checker library.
+ *
+ * Upon checking with the Market server and conferring with the policy, the
+ * library calls a appropriate callback method to communicate the result.
+ */
+public interface LicenseCheckerCallback {
+
+ /**
+ * Allow use. App should proceed as normal.
+ */
+ public void allow();
+
+ /**
+ * Don't allow use. App should inform user and take appropriate action.
+ */
+ public void dontAllow();
+
+ /** Application error codes. */
+ public enum ApplicationErrorCode {
+ /** Package is not installed. */
+ INVALID_PACKAGE_NAME,
+ /** Requested for a package that is not the current app. */
+ NON_MATCHING_UID,
+ /** Market does not know about the package. */
+ NOT_MARKET_MANAGED,
+ /** A previous check request is already in progress.
+ * Only one check is allowed at a time. */
+ CHECK_IN_PROGRESS
+ }
+
+ /**
+ * Error in application code. Caller did not call or set up license
+ * checker correctly. Should be considered fatal.
+ */
+ public void applicationError(ApplicationErrorCode errorCode);
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/LicenseValidator.java b/samples/MarketLicensing/src/com/android/vending/licensing/LicenseValidator.java
new file mode 100755
index 0000000..135d98e
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/LicenseValidator.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+import android.util.Log;
+
+import com.android.vending.licensing.LicenseCheckerCallback.ApplicationErrorCode;
+import com.android.vending.licensing.Policy.LicenseResponse;
+
+/**
+ * Contains data related to a licensing request and methods to verify
+ * and process the response.
+ */
+class LicenseValidator {
+ private static final String TAG = "LicenseValidator";
+
+ // Server response codes.
+ private static final int LICENSED = 0x0;
+ private static final int NOT_LICENSED = 0x1;
+ private static final int LICENSED_OLD_KEY = 0x2;
+ private static final int ERROR_NOT_MARKET_MANAGED = 0x3;
+ private static final int ERROR_INVALID_KEYS = 0x4;
+ private static final int ERROR_OVER_QUOTA = 0x5;
+
+ private static final int ERROR_CONTACTING_SERVER = 0x101;
+ private static final int ERROR_INVALID_PACKAGE_NAME = 0x102;
+ private static final int ERROR_NON_MATCHING_UID = 0x103;
+
+ private final Policy mPolicy;
+ private final LicenseCheckerCallback mCallback;
+ private final int mNonce;
+ private final String mPackageName;
+ private final String mVersionCode;
+
+ LicenseValidator(Policy policy, LicenseCheckerCallback callback, int nonce, String packageName,
+ String versionCode) {
+ mPolicy = policy;
+ mCallback = callback;
+ mNonce = nonce;
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ public LicenseCheckerCallback getCallback() {
+ return mCallback;
+ }
+
+ public int getNonce() {
+ return mNonce;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Verifies the response from server and calls appropriate callback method.
+ *
+ * @param responseCode server response code
+ * @param signedData signed data from server
+ * @param signature server signature
+ */
+ public void verify(int responseCode, String signedData, String signature) {
+ // Parse and validate response.
+ // TODO(jyum): decode data with signature.
+ // TODO(jyum): verify timestamp is within reason. However, relying
+ // on device clock may lead to problems?
+ ResponseData data;
+ try {
+ data = ResponseData.parse(signedData);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Could not parse response.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (data.responseCode != responseCode) {
+ Log.e(TAG, "Response codes don't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (data.nonce != mNonce) {
+ Log.e(TAG, "Nonce doesn't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (!data.packageName.equals(mPackageName)) {
+ Log.e(TAG, "Package name doesn't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ if (!data.versionCode.equals(mVersionCode)) {
+ Log.e(TAG, "Version codes don't match.");
+ handleInvalidResponse();
+ return;
+ }
+
+ switch (responseCode) {
+ case LICENSED:
+ case LICENSED_OLD_KEY:
+ handleResponse(LicenseResponse.LICENSED);
+ break;
+ case NOT_LICENSED:
+ handleResponse(LicenseResponse.NOT_LICENSED);
+ break;
+ case ERROR_CONTACTING_SERVER:
+ handleResponse(LicenseResponse.CLIENT_RETRY);
+ break;
+ case ERROR_INVALID_KEYS:
+ case ERROR_OVER_QUOTA:
+ handleResponse(LicenseResponse.SERVER_RETRY);
+ break;
+ case ERROR_INVALID_PACKAGE_NAME:
+ handleApplicationError(ApplicationErrorCode.INVALID_PACKAGE_NAME);
+ break;
+ case ERROR_NON_MATCHING_UID:
+ handleApplicationError(ApplicationErrorCode.NON_MATCHING_UID);
+ break;
+ case ERROR_NOT_MARKET_MANAGED:
+ handleApplicationError(ApplicationErrorCode.NOT_MARKET_MANAGED);
+ break;
+ default:
+ Log.e(TAG, "Unknown response code for license check.");
+ handleInvalidResponse();
+ }
+ }
+
+ /**
+ * Confers with policy and calls appropriate callback method.
+ *
+ * @param response
+ */
+ private void handleResponse(LicenseResponse response) {
+ if (mPolicy.allowAccess(response)) {
+ mCallback.allow();
+ } else {
+ mCallback.dontAllow();
+ }
+ }
+
+ private void handleApplicationError(ApplicationErrorCode code) {
+ mCallback.applicationError(code);
+ }
+
+ private void handleInvalidResponse() {
+ mCallback.dontAllow();
+ }
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/Policy.java b/samples/MarketLicensing/src/com/android/vending/licensing/Policy.java
new file mode 100755
index 0000000..461c08e
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/Policy.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+/**
+ * Policy used by {@link LicenseChecker} to determine whether a user should
+ * have access to the application.
+ */
+public interface Policy {
+
+ /**
+ * Result of a license check.
+ */
+ public enum LicenseResponse {
+ /**
+ * User is licensed to use the app.
+ */
+ LICENSED,
+ /**
+ * User is not licensed to use the app.
+ */
+ NOT_LICENSED,
+ /**
+ * Retryable error on the client side e.g. no network.
+ */
+ CLIENT_RETRY,
+ /**
+ * Retryable error on the server side e.g. application is over request
+ * quota.
+ */
+ SERVER_RETRY,
+ }
+
+ /**
+ * Determines whether the user should be allowed access.
+ *
+ * @param response result of the license check request
+ * @return true iff access should be allowed
+ */
+ boolean allowAccess(LicenseResponse response);
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/ResponseData.java b/samples/MarketLicensing/src/com/android/vending/licensing/ResponseData.java
new file mode 100755
index 0000000..3882d56
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/ResponseData.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+import android.text.TextUtils;
+
+/**
+ * ResponseData from licensing server.
+ */
+class ResponseData {
+
+ public int responseCode;
+ public int nonce;
+ public String packageName;
+ public String versionCode;
+ public String userId;
+ public long timestamp;
+ /** Response-specific data. */
+ public String extra;
+
+ /**
+ * Parses response string into ResponseData.
+ *
+ * @param responseData response data string
+ * @throws IllegalArgumentException upon parsing error
+ * @return ResponseData object
+ */
+ public static ResponseData parse(String responseData) {
+ // Must parse out main response data and response-specific data.
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
+ splitter.setString(responseData);
+ Iterator<String> it = splitter.iterator();
+ if (!it.hasNext()) {
+ throw new IllegalArgumentException("Blank response.");
+ }
+ final String mainData = it.next();
+
+ // Response-specific (extra) data is optional.
+ String extraData = "";
+ if (it.hasNext()) {
+ extraData = it.next();
+ }
+
+ String [] fields = TextUtils.split(mainData, Pattern.quote("|"));
+ if (fields.length < 5) {
+ throw new IllegalArgumentException("Wrong number of fields.");
+ }
+
+ ResponseData data = new ResponseData();
+ data.extra = extraData;
+ data.responseCode = Integer.parseInt(fields[0]);
+ data.nonce = Integer.parseInt(fields[1]);
+ data.packageName = fields[2];
+ data.versionCode = fields[3];
+ // TODO(jyum): userId is not there yet.
+ // data.userId = fields[4];
+ data.timestamp = Long.parseLong(fields[4]);
+
+ return data;
+ }
+
+ @Override
+ public String toString() {
+ return TextUtils.join("|", new Object [] { responseCode, nonce, packageName, versionCode,
+ userId, timestamp });
+ }
+}
diff --git a/samples/MarketLicensing/src/com/android/vending/licensing/StrictPolicy.java b/samples/MarketLicensing/src/com/android/vending/licensing/StrictPolicy.java
new file mode 100755
index 0000000..ddff8e9
--- /dev/null
+++ b/samples/MarketLicensing/src/com/android/vending/licensing/StrictPolicy.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.licensing;
+
+/**
+ * Strict policy.
+ *
+ * Should never be used in a real application as it strictly disallows access
+ * upon retryable errors such as no connection present.
+ */
+public class StrictPolicy implements Policy {
+
+ public boolean allowAccess(LicenseResponse response) {
+ return LicenseResponse.LICENSED == response;
+ }
+}