/// <summary>
        /// Verifies the response from server and calls appropriate callback method.
        /// </summary>
        /// <param name="publicKey">
        /// public key associated with the developer account
        /// </param>
        /// <param name="responseCode">
        /// server response code
        /// </param>
        /// <param name="signedData">
        /// signed data from server
        /// </param>
        /// <param name="signature">
        /// server signature
        /// </param>
        public void Verify(IPublicKey publicKey, ServerResponseCode responseCode, string signedData, string signature)
        {
            string userId = null;

            // Skip signature check for unsuccessful requests
            ResponseData data = null;

            if (responseCode == ServerResponseCode.Licensed || responseCode == ServerResponseCode.NotLicensed ||
                responseCode == ServerResponseCode.LicensedOldKey)
            {
                // Verify signature.
                try
                {
                    Signature sig = Signature.GetInstance(SignatureAlgorithm);
                    sig.InitVerify(publicKey);
                    sig.Update(new Java.Lang.String(signedData).GetBytes());

                    if (!sig.Verify(Convert.FromBase64String(signature)))
                    {
                        System.Diagnostics.Debug.WriteLine("Signature verification failed.");
                        this.HandleInvalidResponse();
                        return;
                    }
                }
                catch (NoSuchAlgorithmException e)
                {
                    // This can't happen on an Android compatible device.
                    throw new RuntimeException(e);
                }
                catch (InvalidKeyException)
                {
                    this.HandleApplicationError(CallbackErrorCode.InvalidPublicKey);
                    return;
                }
                catch (SignatureException e)
                {
                    throw new RuntimeException(e);
                }
                catch (FormatException)
                {
                    System.Diagnostics.Debug.WriteLine("Could not Base64-decode signature.");
                    this.HandleInvalidResponse();
                    return;
                }

                // Parse and validate response.
                try
                {
                    data = ResponseData.Parse(signedData);
                }
                catch (IllegalArgumentException)
                {
                    System.Diagnostics.Debug.WriteLine("Could not parse response.");
                    this.HandleInvalidResponse();
                    return;
                }

                if (data.ResponseCode != responseCode)
                {
                    System.Diagnostics.Debug.WriteLine("Response codes don't match.");
                    this.HandleInvalidResponse();
                    return;
                }

                if (data.NumberUsedOnce != this.numberUsedOnce)
                {
                    System.Diagnostics.Debug.WriteLine("NumberUsedOnce doesn't match.");
                    this.HandleInvalidResponse();
                    return;
                }

                if (data.PackageName != this.packageName)
                {
                    System.Diagnostics.Debug.WriteLine("Package name doesn't match.");
                    this.HandleInvalidResponse();
                    return;
                }

                if (data.VersionCode != this.versionCode)
                {
                    System.Diagnostics.Debug.WriteLine("Version codes don't match.");
                    this.HandleInvalidResponse();
                    return;
                }

                // Application-specific user identifier.
                userId = data.UserId;
                if (string.IsNullOrEmpty(userId))
                {
                    System.Diagnostics.Debug.WriteLine("User identifier is empty.");
                    this.HandleInvalidResponse();
                    return;
                }
            }

            switch (responseCode)
            {
            case ServerResponseCode.Licensed:
            case ServerResponseCode.LicensedOldKey:
                PolicyServerResponse limiterResponse = this.deviceLimiter.IsDeviceAllowed(userId);
                this.HandleResponse(limiterResponse, data);
                break;

            case ServerResponseCode.NotLicensed:
                this.HandleResponse(PolicyServerResponse.NotLicensed, data);
                break;

            case ServerResponseCode.ErrorContactingServer:
                System.Diagnostics.Debug.WriteLine("Error contacting licensing server.");
                this.HandleResponse(PolicyServerResponse.Retry, data);
                break;

            case ServerResponseCode.ServerFailure:
                System.Diagnostics.Debug.WriteLine("An error has occurred on the licensing server.");
                this.HandleResponse(PolicyServerResponse.Retry, data);
                break;

            case ServerResponseCode.OverQuota:
                System.Diagnostics.Debug.WriteLine(
                    "Licensing server is refusing to talk to this device, over quota.");
                this.HandleResponse(PolicyServerResponse.Retry, data);
                break;

            case ServerResponseCode.InvalidPackageName:
                this.HandleApplicationError(CallbackErrorCode.InvalidPackageName);
                break;

            case ServerResponseCode.NonMatchingUid:
                this.HandleApplicationError(CallbackErrorCode.ErrorNonMatchingUid);
                break;

            case ServerResponseCode.NotMarketManaged:
                this.HandleApplicationError(CallbackErrorCode.NotMarketManaged);
                break;

            default:
                System.Diagnostics.Debug.WriteLine("Unknown response code for license check.");
                this.HandleInvalidResponse();
                break;
            }
        }
        /// <summary>
        /// Parses response string into ResponseData.
        /// </summary>
        /// <param name="responseData">
        /// response data string
        /// </param>
        /// <returns>
        /// ResponseData object
        /// </returns>
        /// <exception cref="ArgumentException">
        /// upon parsing error
        /// </exception>
        public static ResponseData Parse(string responseData)
        {
            // Must parse out main response data and response-specific data.
            int index = responseData.IndexOf(':');
            string mainData = responseData;
            string extraData = string.Empty;
            if (index != -1)
            {
                mainData = responseData.Substring(0, index);
                extraData = index < responseData.Length ? responseData.Substring(index + 1) : string.Empty;
            }

            string[] fields = mainData.Split('|');
            if (fields.Length < 6)
            {
                throw new ArgumentException("Wrong number of fields.");
            }

            var data = new ResponseData
                {
                    Extra = extraData, 
                    ResponseCode = (ServerResponseCode)Enum.Parse(typeof(ServerResponseCode), fields[0]), 
                    NumberUsedOnce = int.Parse(fields[1]), 
                    PackageName = fields[2], 
                    VersionCode = fields[3], 
                    // Application-specific user identifier.
                    UserId = fields[4], 
                    TimeStamp = long.Parse(fields[5])
                };

            return data;
        }
        /// <summary>
        /// Confers with policy and calls appropriate callback method.
        /// </summary>
        /// <param name="response">
        /// The response.
        /// </param>
        /// <param name="rawData">
        /// The raw Data.
        /// </param>
        private void HandleResponse(PolicyServerResponse response, ResponseData rawData)
        {
            // Update policy data and increment retry counter (if needed)
            this.policy.ProcessServerResponse(response, rawData);

            // Given everything we know, including cached data, ask the policy if we
            // should grant
            // access.
            if (this.policy.AllowAccess())
            {
                this.licenseCheckerCallback.Allow(response);
            }
            else
            {
                this.licenseCheckerCallback.DontAllow(response);
            }
        }