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;

+    }

+}