summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2019-08-27 13:31:38 +0200
committerRémi Verschelde <rverschelde@gmail.com>2019-08-27 13:44:16 +0200
commit6f0367052a3450d732aa197f20251b168e1094b4 (patch)
treeae29a032bea0d32a8c102533d2d86c236fcb67f5
parent071ebb1e4871431e7edf7f679afd02e594ea5af9 (diff)
Android: Resync Google licensing lib with upstream, unmodified
It had been synced with style changes (spaces -> tabs), not sure why I accepted to merge it this way back then... Synced with https://github.com/google/play-licensing/commit/eb57657f666363914085cdde49d875cf49f5ab06, same as before. Custom-changes will be reapplied in the next commit, if relevant.
-rw-r--r--platform/android/java/aidl/com/android/vending/licensing/ILicenseResultListener.aidl (renamed from platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.aidl)2
-rw-r--r--platform/android/java/aidl/com/android/vending/licensing/ILicensingService.aidl (renamed from platform/android/java/src/com/google/android/vending/licensing/ILicensingService.aidl)2
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java124
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java541
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java4
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java100
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java100
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java555
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java26
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java367
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java6
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java8
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/Policy.java26
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java81
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/ResponseData.java83
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java335
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java75
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/ValidationException.java14
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/util/Base64.java700
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java14
-rw-r--r--platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java50
21 files changed, 1513 insertions, 1700 deletions
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.aidl b/platform/android/java/aidl/com/android/vending/licensing/ILicenseResultListener.aidl
index c816558afc..869cb16f68 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.aidl
+++ b/platform/android/java/aidl/com/android/vending/licensing/ILicenseResultListener.aidl
@@ -16,8 +16,6 @@
package com.android.vending.licensing;
-// Android library projects do not yet support AIDL, so this has been
-// precompiled into the src directory.
oneway interface ILicenseResultListener {
void verifyLicense(int responseCode, String signedData, String signature);
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.aidl b/platform/android/java/aidl/com/android/vending/licensing/ILicensingService.aidl
index 664510ce0c..9541a2090c 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.aidl
+++ b/platform/android/java/aidl/com/android/vending/licensing/ILicensingService.aidl
@@ -18,8 +18,6 @@ package com.android.vending.licensing;
import com.android.vending.licensing.ILicenseResultListener;
-// Android library projects do not yet support AIDL, so this has been
-// precompiled into the src directory.
oneway interface ILicensingService {
void checkLicense(long nonce, String packageName, in ILicenseResultListener listener);
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java
index feba3034c3..d6ccb0c5e4 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/AESObfuscator.java
@@ -36,75 +36,75 @@ import javax.crypto.spec.SecretKeySpec;
* An Obfuscator that uses AES to encrypt data.
*/
public class AESObfuscator implements Obfuscator {
- private static final String UTF8 = "UTF-8";
- private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC";
- private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
- private static final byte[] IV = { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 };
- private static final String header = "com.google.android.vending.licensing.AESObfuscator-1|";
+ private static final String UTF8 = "UTF-8";
+ private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC";
+ private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
+ private static final byte[] IV =
+ { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 };
+ private static final String header = "com.google.android.vending.licensing.AESObfuscator-1|";
- private Cipher mEncryptor;
- private Cipher mDecryptor;
+ private Cipher mEncryptor;
+ private Cipher mDecryptor;
- /**
+ /**
* @param salt an array of random bytes to use for each (un)obfuscation
* @param applicationId application identifier, e.g. the package name
* @param deviceId device identifier. Use as many sources as possible to
* create this unique identifier.
*/
- public AESObfuscator(byte[] salt, String applicationId, String deviceId) {
- try {
- SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM);
- KeySpec keySpec =
- new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256);
- SecretKey tmp = factory.generateSecret(keySpec);
- SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
- mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM);
- mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV));
- mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM);
- mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV));
- } catch (GeneralSecurityException e) {
- // This can't happen on a compatible Android device.
- throw new RuntimeException("Invalid environment", e);
- }
- }
+ public AESObfuscator(byte[] salt, String applicationId, String deviceId) {
+ try {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM);
+ KeySpec keySpec =
+ new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256);
+ SecretKey tmp = factory.generateSecret(keySpec);
+ SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
+ mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM);
+ mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV));
+ mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM);
+ mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV));
+ } catch (GeneralSecurityException e) {
+ // This can't happen on a compatible Android device.
+ throw new RuntimeException("Invalid environment", e);
+ }
+ }
- public String obfuscate(String original, String key) {
- if (original == null) {
- return null;
- }
- try {
- // Header is appended as an integrity check
- return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8)));
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Invalid environment", e);
- } catch (GeneralSecurityException e) {
- throw new RuntimeException("Invalid environment", e);
- }
- }
+ public String obfuscate(String original, String key) {
+ if (original == null) {
+ return null;
+ }
+ try {
+ // Header is appended as an integrity check
+ return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8)));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Invalid environment", e);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Invalid environment", e);
+ }
+ }
- public String unobfuscate(String obfuscated, String key) throws ValidationException {
- if (obfuscated == null) {
- return null;
- }
- try {
- String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8);
- // Check for presence of header. This serves as a final integrity check, for cases
- // where the block size is correct during decryption.
- int headerIndex = result.indexOf(header + key);
- if (headerIndex != 0) {
- throw new ValidationException("Header not found (invalid data or key)"
- + ":" +
- obfuscated);
- }
- return result.substring(header.length() + key.length(), result.length());
- } catch (Base64DecoderException e) {
- throw new ValidationException(e.getMessage() + ":" + obfuscated);
- } catch (IllegalBlockSizeException e) {
- throw new ValidationException(e.getMessage() + ":" + obfuscated);
- } catch (BadPaddingException e) {
- throw new ValidationException(e.getMessage() + ":" + obfuscated);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("Invalid environment", e);
- }
- }
+ public String unobfuscate(String obfuscated, String key) throws ValidationException {
+ if (obfuscated == null) {
+ return null;
+ }
+ try {
+ String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8);
+ // Check for presence of header. This serves as a final integrity check, for cases
+ // where the block size is correct during decryption.
+ int headerIndex = result.indexOf(header+key);
+ if (headerIndex != 0) {
+ throw new ValidationException("Header not found (invalid data or key)" + ":" +
+ obfuscated);
+ }
+ return result.substring(header.length()+key.length(), result.length());
+ } catch (Base64DecoderException e) {
+ throw new ValidationException(e.getMessage() + ":" + obfuscated);
+ } catch (IllegalBlockSizeException e) {
+ throw new ValidationException(e.getMessage() + ":" + obfuscated);
+ } catch (BadPaddingException e) {
+ throw new ValidationException(e.getMessage() + ":" + obfuscated);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Invalid environment", e);
+ }
+ }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java
index 2c60e7e4b8..37fad8926a 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/APKExpansionPolicy.java
@@ -46,73 +46,73 @@ import java.util.Vector;
*/
public class APKExpansionPolicy implements Policy {
- private static final String TAG = "APKExpansionPolicy";
- private static final String PREFS_FILE = "com.google.android.vending.licensing.APKExpansionPolicy";
- private static final String PREF_LAST_RESPONSE = "lastResponse";
- private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
- private static final String PREF_RETRY_UNTIL = "retryUntil";
- private static final String PREF_MAX_RETRIES = "maxRetries";
- private static final String PREF_RETRY_COUNT = "retryCount";
- private static final String PREF_LICENSING_URL = "licensingUrl";
- private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
- private static final String DEFAULT_RETRY_UNTIL = "0";
- private static final String DEFAULT_MAX_RETRIES = "0";
- private static final String DEFAULT_RETRY_COUNT = "0";
-
- private static final long MILLIS_PER_MINUTE = 60 * 1000;
-
- private long mValidityTimestamp;
- private long mRetryUntil;
- private long mMaxRetries;
- private long mRetryCount;
- private long mLastResponseTime = 0;
- private int mLastResponse;
- private String mLicensingUrl;
- private PreferenceObfuscator mPreferences;
- private Vector<String> mExpansionURLs = new Vector<String>();
- private Vector<String> mExpansionFileNames = new Vector<String>();
- private Vector<Long> mExpansionFileSizes = new Vector<Long>();
-
- /**
+ private static final String TAG = "APKExpansionPolicy";
+ private static final String PREFS_FILE = "com.google.android.vending.licensing.APKExpansionPolicy";
+ private static final String PREF_LAST_RESPONSE = "lastResponse";
+ private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
+ private static final String PREF_RETRY_UNTIL = "retryUntil";
+ private static final String PREF_MAX_RETRIES = "maxRetries";
+ private static final String PREF_RETRY_COUNT = "retryCount";
+ private static final String PREF_LICENSING_URL = "licensingUrl";
+ private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
+ private static final String DEFAULT_RETRY_UNTIL = "0";
+ private static final String DEFAULT_MAX_RETRIES = "0";
+ private static final String DEFAULT_RETRY_COUNT = "0";
+
+ private static final long MILLIS_PER_MINUTE = 60 * 1000;
+
+ private long mValidityTimestamp;
+ private long mRetryUntil;
+ private long mMaxRetries;
+ private long mRetryCount;
+ private long mLastResponseTime = 0;
+ private int mLastResponse;
+ private String mLicensingUrl;
+ private PreferenceObfuscator mPreferences;
+ private Vector<String> mExpansionURLs = new Vector<String>();
+ private Vector<String> mExpansionFileNames = new Vector<String>();
+ private Vector<Long> mExpansionFileSizes = new Vector<Long>();
+
+ /**
* The design of the protocol supports n files. Currently the market can
* only deliver two files. To accommodate this, we have these two constants,
* but the order is the only relevant thing here.
*/
- public static final int MAIN_FILE_URL_INDEX = 0;
- public static final int PATCH_FILE_URL_INDEX = 1;
+ public static final int MAIN_FILE_URL_INDEX = 0;
+ public static final int PATCH_FILE_URL_INDEX = 1;
- /**
+ /**
* @param context The context for the current application
* @param obfuscator An obfuscator to be used with preferences.
*/
- public APKExpansionPolicy(Context context, Obfuscator obfuscator) {
- // Import old values
- SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
- mPreferences = new PreferenceObfuscator(sp, obfuscator);
- mLastResponse = Integer.parseInt(
- mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
- mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
- DEFAULT_VALIDITY_TIMESTAMP));
- mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
- mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
- mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
- mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null);
- }
-
- /**
+ public APKExpansionPolicy(Context context, Obfuscator obfuscator) {
+ // Import old values
+ SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
+ mPreferences = new PreferenceObfuscator(sp, obfuscator);
+ mLastResponse = Integer.parseInt(
+ mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
+ mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
+ DEFAULT_VALIDITY_TIMESTAMP));
+ mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
+ mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
+ mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
+ mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null);
+ }
+
+ /**
* We call this to guarantee that we fetch a fresh policy from the server.
* This is to be used if the URL is invalid.
*/
- public void resetPolicy() {
- mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY));
- setRetryUntil(DEFAULT_RETRY_UNTIL);
- setMaxRetries(DEFAULT_MAX_RETRIES);
- setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT));
- setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
- mPreferences.commit();
- }
-
- /**
+ public void resetPolicy() {
+ mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY));
+ setRetryUntil(DEFAULT_RETRY_UNTIL);
+ setMaxRetries(DEFAULT_MAX_RETRIES);
+ setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT));
+ setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
+ mPreferences.commit();
+ }
+
+ /**
* Process a new response from the license server.
* <p>
* This data will be used for computing future policy decisions. The
@@ -129,187 +129,187 @@ public class APKExpansionPolicy implements Policy {
* @param response the result from validating the server response
* @param rawData the raw server response data
*/
- public void processServerResponse(int response,
- com.google.android.vending.licensing.ResponseData rawData) {
-
- // Update retry counter
- if (response != Policy.RETRY) {
- setRetryCount(0);
- } else {
- setRetryCount(mRetryCount + 1);
- }
-
- // Update server policy data
- Map<String, String> extras = decodeExtras(rawData);
- if (response == Policy.LICENSED) {
- mLastResponse = response;
- // Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
- setLicensingUrl(null);
- setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE));
- Set<String> keys = extras.keySet();
- for (String key : keys) {
- if (key.equals("VT")) {
- setValidityTimestamp(extras.get(key));
- } else if (key.equals("GT")) {
- setRetryUntil(extras.get(key));
- } else if (key.equals("GR")) {
- setMaxRetries(extras.get(key));
- } else if (key.startsWith("FILE_URL")) {
- int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1;
- setExpansionURL(index, extras.get(key));
- } else if (key.startsWith("FILE_NAME")) {
- int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1;
- setExpansionFileName(index, extras.get(key));
- } else if (key.startsWith("FILE_SIZE")) {
- int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1;
- setExpansionFileSize(index, Long.parseLong(extras.get(key)));
- }
- }
- } else if (response == Policy.NOT_LICENSED) {
- // Clear out stale retry params
- setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
- setRetryUntil(DEFAULT_RETRY_UNTIL);
- setMaxRetries(DEFAULT_MAX_RETRIES);
- // Update the licensing URL
- setLicensingUrl(extras.get("LU"));
- }
-
- setLastResponse(response);
- mPreferences.commit();
- }
-
- /**
+ public void processServerResponse(int response,
+ com.google.android.vending.licensing.ResponseData rawData) {
+
+ // Update retry counter
+ if (response != Policy.RETRY) {
+ setRetryCount(0);
+ } else {
+ setRetryCount(mRetryCount + 1);
+ }
+
+ // Update server policy data
+ Map<String, String> extras = decodeExtras(rawData);
+ if (response == Policy.LICENSED) {
+ mLastResponse = response;
+ // Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
+ setLicensingUrl(null);
+ setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE));
+ Set<String> keys = extras.keySet();
+ for (String key : keys) {
+ if (key.equals("VT")) {
+ setValidityTimestamp(extras.get(key));
+ } else if (key.equals("GT")) {
+ setRetryUntil(extras.get(key));
+ } else if (key.equals("GR")) {
+ setMaxRetries(extras.get(key));
+ } else if (key.startsWith("FILE_URL")) {
+ int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1;
+ setExpansionURL(index, extras.get(key));
+ } else if (key.startsWith("FILE_NAME")) {
+ int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1;
+ setExpansionFileName(index, extras.get(key));
+ } else if (key.startsWith("FILE_SIZE")) {
+ int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1;
+ setExpansionFileSize(index, Long.parseLong(extras.get(key)));
+ }
+ }
+ } else if (response == Policy.NOT_LICENSED) {
+ // Clear out stale retry params
+ setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
+ setRetryUntil(DEFAULT_RETRY_UNTIL);
+ setMaxRetries(DEFAULT_MAX_RETRIES);
+ // Update the licensing URL
+ setLicensingUrl(extras.get("LU"));
+ }
+
+ setLastResponse(response);
+ mPreferences.commit();
+ }
+
+ /**
* Set the last license response received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param l the response
*/
- private void setLastResponse(int l) {
- mLastResponseTime = System.currentTimeMillis();
- mLastResponse = l;
- mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l));
- }
+ private void setLastResponse(int l) {
+ mLastResponseTime = System.currentTimeMillis();
+ mLastResponse = l;
+ mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l));
+ }
- /**
+ /**
* Set the current retry count and add to preferences. You must manually
* call PreferenceObfuscator.commit() to commit these changes to disk.
*
* @param c the new retry count
*/
- private void setRetryCount(long c) {
- mRetryCount = c;
- mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c));
- }
+ private void setRetryCount(long c) {
+ mRetryCount = c;
+ mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c));
+ }
- public long getRetryCount() {
- return mRetryCount;
- }
+ public long getRetryCount() {
+ return mRetryCount;
+ }
- /**
+ /**
* Set the last validity timestamp (VT) received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param validityTimestamp the VT string received
*/
- private void setValidityTimestamp(String validityTimestamp) {
- Long lValidityTimestamp;
- try {
- lValidityTimestamp = Long.parseLong(validityTimestamp);
- } catch (NumberFormatException e) {
- // No response or not parseable, expire in one minute.
- Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute");
- lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE;
- validityTimestamp = Long.toString(lValidityTimestamp);
- }
-
- mValidityTimestamp = lValidityTimestamp;
- mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp);
- }
-
- public long getValidityTimestamp() {
- return mValidityTimestamp;
- }
-
- /**
+ private void setValidityTimestamp(String validityTimestamp) {
+ Long lValidityTimestamp;
+ try {
+ lValidityTimestamp = Long.parseLong(validityTimestamp);
+ } catch (NumberFormatException e) {
+ // No response or not parseable, expire in one minute.
+ Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute");
+ lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE;
+ validityTimestamp = Long.toString(lValidityTimestamp);
+ }
+
+ mValidityTimestamp = lValidityTimestamp;
+ mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp);
+ }
+
+ public long getValidityTimestamp() {
+ return mValidityTimestamp;
+ }
+
+ /**
* Set the retry until timestamp (GT) received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param retryUntil the GT string received
*/
- private void setRetryUntil(String retryUntil) {
- Long lRetryUntil;
- try {
- lRetryUntil = Long.parseLong(retryUntil);
- } catch (NumberFormatException e) {
- // No response or not parseable, expire immediately
- Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled");
- retryUntil = "0";
- lRetryUntil = 0l;
- }
-
- mRetryUntil = lRetryUntil;
- mPreferences.putString(PREF_RETRY_UNTIL, retryUntil);
- }
-
- public long getRetryUntil() {
- return mRetryUntil;
- }
-
- /**
+ private void setRetryUntil(String retryUntil) {
+ Long lRetryUntil;
+ try {
+ lRetryUntil = Long.parseLong(retryUntil);
+ } catch (NumberFormatException e) {
+ // No response or not parseable, expire immediately
+ Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled");
+ retryUntil = "0";
+ lRetryUntil = 0l;
+ }
+
+ mRetryUntil = lRetryUntil;
+ mPreferences.putString(PREF_RETRY_UNTIL, retryUntil);
+ }
+
+ public long getRetryUntil() {
+ return mRetryUntil;
+ }
+
+ /**
* Set the max retries value (GR) as received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param maxRetries the GR string received
*/
- private void setMaxRetries(String maxRetries) {
- Long lMaxRetries;
- try {
- lMaxRetries = Long.parseLong(maxRetries);
- } catch (NumberFormatException e) {
- // No response or not parseable, expire immediately
- Log.w(TAG, "Licence retry count (GR) missing, grace period disabled");
- maxRetries = "0";
- lMaxRetries = 0l;
- }
-
- mMaxRetries = lMaxRetries;
- mPreferences.putString(PREF_MAX_RETRIES, maxRetries);
- }
-
- public long getMaxRetries() {
- return mMaxRetries;
- }
-
- /**
+ private void setMaxRetries(String maxRetries) {
+ Long lMaxRetries;
+ try {
+ lMaxRetries = Long.parseLong(maxRetries);
+ } catch (NumberFormatException e) {
+ // No response or not parseable, expire immediately
+ Log.w(TAG, "Licence retry count (GR) missing, grace period disabled");
+ maxRetries = "0";
+ lMaxRetries = 0l;
+ }
+
+ mMaxRetries = lMaxRetries;
+ mPreferences.putString(PREF_MAX_RETRIES, maxRetries);
+ }
+
+ public long getMaxRetries() {
+ return mMaxRetries;
+ }
+
+ /**
* Set the licensing URL that displays a Play Store UI for the user to regain app access.
*
* @param url the LU string received
*/
- private void setLicensingUrl(String url) {
- mLicensingUrl = url;
- mPreferences.putString(PREF_LICENSING_URL, url);
- }
+ private void setLicensingUrl(String url) {
+ mLicensingUrl = url;
+ mPreferences.putString(PREF_LICENSING_URL, url);
+ }
- public String getLicensingUrl() {
- return mLicensingUrl;
- }
+ public String getLicensingUrl() {
+ return mLicensingUrl;
+ }
- /**
+ /**
* Gets the count of expansion URLs. Since expansionURLs are not committed
* to preferences, this will return zero if there has been no LVL fetch
* in the current session.
*
* @return the number of expansion URLs. (0,1,2)
*/
- public int getExpansionURLCount() {
- return mExpansionURLs.size();
- }
+ public int getExpansionURLCount() {
+ return mExpansionURLs.size();
+ }
- /**
+ /**
* Gets the expansion URL. Since these URLs are not committed to
* preferences, this will always return null if there has not been an LVL
* fetch in the current session.
@@ -317,14 +317,14 @@ public class APKExpansionPolicy implements Policy {
* @param index the index of the URL to fetch. This value will be either
* MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX
*/
- public String getExpansionURL(int index) {
- if (index < mExpansionURLs.size()) {
- return mExpansionURLs.elementAt(index);
- }
- return null;
- }
-
- /**
+ public String getExpansionURL(int index) {
+ if (index < mExpansionURLs.size()) {
+ return mExpansionURLs.elementAt(index);
+ }
+ return null;
+ }
+
+ /**
* Sets the expansion URL. Expansion URL's are not committed to preferences,
* but are instead intended to be stored when the license response is
* processed by the front-end.
@@ -333,42 +333,42 @@ public class APKExpansionPolicy implements Policy {
* MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX
* @param URL the URL to set
*/
- public void setExpansionURL(int index, String URL) {
- if (index >= mExpansionURLs.size()) {
- mExpansionURLs.setSize(index + 1);
- }
- mExpansionURLs.set(index, URL);
- }
-
- public String getExpansionFileName(int index) {
- if (index < mExpansionFileNames.size()) {
- return mExpansionFileNames.elementAt(index);
- }
- return null;
- }
-
- public void setExpansionFileName(int index, String name) {
- if (index >= mExpansionFileNames.size()) {
- mExpansionFileNames.setSize(index + 1);
- }
- mExpansionFileNames.set(index, name);
- }
-
- public long getExpansionFileSize(int index) {
- if (index < mExpansionFileSizes.size()) {
- return mExpansionFileSizes.elementAt(index);
- }
- return -1;
- }
-
- public void setExpansionFileSize(int index, long size) {
- if (index >= mExpansionFileSizes.size()) {
- mExpansionFileSizes.setSize(index + 1);
- }
- mExpansionFileSizes.set(index, size);
- }
-
- /**
+ public void setExpansionURL(int index, String URL) {
+ if (index >= mExpansionURLs.size()) {
+ mExpansionURLs.setSize(index + 1);
+ }
+ mExpansionURLs.set(index, URL);
+ }
+
+ public String getExpansionFileName(int index) {
+ if (index < mExpansionFileNames.size()) {
+ return mExpansionFileNames.elementAt(index);
+ }
+ return null;
+ }
+
+ public void setExpansionFileName(int index, String name) {
+ if (index >= mExpansionFileNames.size()) {
+ mExpansionFileNames.setSize(index + 1);
+ }
+ mExpansionFileNames.set(index, name);
+ }
+
+ public long getExpansionFileSize(int index) {
+ if (index < mExpansionFileSizes.size()) {
+ return mExpansionFileSizes.elementAt(index);
+ }
+ return -1;
+ }
+
+ public void setExpansionFileSize(int index, long size) {
+ if (index >= mExpansionFileSizes.size()) {
+ mExpansionFileSizes.setSize(index + 1);
+ }
+ mExpansionFileSizes.set(index, size);
+ }
+
+ /**
* {@inheritDoc} This implementation allows access if either:<br>
* <ol>
* <li>a LICENSED response was received within the validity period
@@ -376,38 +376,39 @@ public class APKExpansionPolicy implements Policy {
* the RETRY count or in the RETRY period.
* </ol>
*/
- public boolean allowAccess() {
- long ts = System.currentTimeMillis();
- if (mLastResponse == Policy.LICENSED) {
- // Check if the LICENSED response occurred within the validity
- // timeout.
- if (ts <= mValidityTimestamp) {
- // Cached LICENSED response is still valid.
- return true;
- }
- } else if (mLastResponse == Policy.RETRY &&
- ts < mLastResponseTime + MILLIS_PER_MINUTE) {
- // Only allow access if we are within the retry period or we haven't
- // used up our
- // max retries.
- return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
- }
- return false;
- }
-
- private Map<String, String> decodeExtras(
- com.google.android.vending.licensing.ResponseData rawData) {
- Map<String, String> results = new HashMap<String, String>();
- if (rawData == null) {
- return results;
- }
-
- try {
- URI rawExtras = new URI("?" + rawData.extra);
- URIQueryDecoder.DecodeQuery(rawExtras, results);
- } catch (URISyntaxException e) {
- Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
- }
- return results;
- }
+ public boolean allowAccess() {
+ long ts = System.currentTimeMillis();
+ if (mLastResponse == Policy.LICENSED) {
+ // Check if the LICENSED response occurred within the validity
+ // timeout.
+ if (ts <= mValidityTimestamp) {
+ // Cached LICENSED response is still valid.
+ return true;
+ }
+ } else if (mLastResponse == Policy.RETRY &&
+ ts < mLastResponseTime + MILLIS_PER_MINUTE) {
+ // Only allow access if we are within the retry period or we haven't
+ // used up our
+ // max retries.
+ return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
+ }
+ return false;
+ }
+
+ private Map<String, String> decodeExtras(
+ com.google.android.vending.licensing.ResponseData rawData) {
+ Map<String, String> results = new HashMap<String, String>();
+ if (rawData == null) {
+ return results;
+ }
+
+ try {
+ URI rawExtras = new URI("?" + rawData.extra);
+ URIQueryDecoder.DecodeQuery(rawExtras, results);
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
+ }
+ return results;
+ }
+
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java b/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java
index 2384b8b82f..e5c5e2d7ca 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/DeviceLimiter.java
@@ -37,11 +37,11 @@ package com.google.android.vending.licensing;
*/
public interface DeviceLimiter {
- /**
+ /**
* Checks if this device is allowed to use the given user's license.
*
* @param userId the user whose license the server responded with
* @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs
*/
- int isDeviceAllowed(String userId);
+ int isDeviceAllowed(String userId);
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java b/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java
deleted file mode 100644
index 89edeae1b4..0000000000
--- a/platform/android/java/src/com/google/android/vending/licensing/ILicenseResultListener.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
-*/
-
-/*
- * This file is auto-generated. DO NOT MODIFY.
- * Original file: aidl/ILicenseResultListener.aidl
- */
-package com.google.android.vending.licensing;
-import java.lang.String;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Binder;
-import android.os.Parcel;
-public interface ILicenseResultListener extends android.os.IInterface {
- /** Local-side IPC implementation stub class. */
- public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicenseResultListener {
- private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener";
- /** Construct the stub at attach it to the interface. */
- public Stub() {
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an ILicenseResultListener interface,
- * generating a proxy if needed.
- */
- public static com.google.android.vending.licensing.ILicenseResultListener asInterface(android.os.IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof com.google.android.vending.licensing.ILicenseResultListener))) {
- return ((com.google.android.vending.licensing.ILicenseResultListener)iin);
- }
- return new com.google.android.vending.licensing.ILicenseResultListener.Stub.Proxy(obj);
- }
- public android.os.IBinder asBinder() {
- return this;
- }
- public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
- switch (code) {
- case INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_verifyLicense: {
- data.enforceInterface(DESCRIPTOR);
- int _arg0;
- _arg0 = data.readInt();
- java.lang.String _arg1;
- _arg1 = data.readString();
- java.lang.String _arg2;
- _arg2 = data.readString();
- this.verifyLicense(_arg0, _arg1, _arg2);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- private static class Proxy implements com.google.android.vending.licensing.ILicenseResultListener {
- private android.os.IBinder mRemote;
- Proxy(android.os.IBinder remote) {
- mRemote = remote;
- }
- public android.os.IBinder asBinder() {
- return mRemote;
- }
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
- public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException {
- android.os.Parcel _data = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeInt(responseCode);
- _data.writeString(signedData);
- _data.writeString(signature);
- mRemote.transact(Stub.TRANSACTION_verifyLicense, _data, null, IBinder.FLAG_ONEWAY);
- } finally {
- _data.recycle();
- }
- }
- }
- static final int TRANSACTION_verifyLicense = (IBinder.FIRST_CALL_TRANSACTION + 0);
- }
- public void verifyLicense(int responseCode, java.lang.String signedData, java.lang.String signature) throws android.os.RemoteException;
-}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java b/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java
deleted file mode 100644
index 8b7cc83541..0000000000
--- a/platform/android/java/src/com/google/android/vending/licensing/ILicensingService.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
-*/
-
-/*
- * This file is auto-generated. DO NOT MODIFY.
- * Original file: aidl/ILicensingService.aidl
- */
-package com.google.android.vending.licensing;
-import java.lang.String;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Binder;
-import android.os.Parcel;
-public interface ILicensingService extends android.os.IInterface {
- /** Local-side IPC implementation stub class. */
- public static abstract class Stub extends android.os.Binder implements com.google.android.vending.licensing.ILicensingService {
- private static final java.lang.String DESCRIPTOR = "com.android.vending.licensing.ILicensingService";
- /** Construct the stub at attach it to the interface. */
- public Stub() {
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an ILicensingService interface,
- * generating a proxy if needed.
- */
- public static com.google.android.vending.licensing.ILicensingService asInterface(android.os.IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof com.google.android.vending.licensing.ILicensingService))) {
- return ((com.google.android.vending.licensing.ILicensingService)iin);
- }
- return new com.google.android.vending.licensing.ILicensingService.Stub.Proxy(obj);
- }
- public android.os.IBinder asBinder() {
- return this;
- }
- public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
- switch (code) {
- case INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_checkLicense: {
- data.enforceInterface(DESCRIPTOR);
- long _arg0;
- _arg0 = data.readLong();
- java.lang.String _arg1;
- _arg1 = data.readString();
- com.google.android.vending.licensing.ILicenseResultListener _arg2;
- _arg2 = com.google.android.vending.licensing.ILicenseResultListener.Stub.asInterface(data.readStrongBinder());
- this.checkLicense(_arg0, _arg1, _arg2);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- private static class Proxy implements com.google.android.vending.licensing.ILicensingService {
- private android.os.IBinder mRemote;
- Proxy(android.os.IBinder remote) {
- mRemote = remote;
- }
- public android.os.IBinder asBinder() {
- return mRemote;
- }
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
- public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException {
- android.os.Parcel _data = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeLong(nonce);
- _data.writeString(packageName);
- _data.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
- mRemote.transact(Stub.TRANSACTION_checkLicense, _data, null, IBinder.FLAG_ONEWAY);
- } finally {
- _data.recycle();
- }
- }
- }
- static final int TRANSACTION_checkLicense = (IBinder.FIRST_CALL_TRANSACTION + 0);
- }
- public void checkLicense(long nonce, java.lang.String packageName, com.google.android.vending.licensing.ILicenseResultListener listener) throws android.os.RemoteException;
-}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java
index 8fc8ae86a2..15017b3425 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/LicenseChecker.java
@@ -29,8 +29,8 @@ import android.os.RemoteException;
import android.provider.Settings.Secure;
import android.util.Log;
-import com.google.android.vending.licensing.ILicenseResultListener;
-import com.google.android.vending.licensing.ILicensingService;
+import com.android.vending.licensing.ILicenseResultListener;
+import com.android.vending.licensing.ILicensingService;
import com.google.android.vending.licensing.util.Base64;
import com.google.android.vending.licensing.util.Base64DecoderException;
@@ -58,73 +58,73 @@ import java.util.Set;
* public key is obtainable from the publisher site.
*/
public class LicenseChecker implements ServiceConnection {
- private static final String TAG = "LicenseChecker";
+ private static final String TAG = "LicenseChecker";
- private static final String KEY_FACTORY_ALGORITHM = "RSA";
+ private static final String KEY_FACTORY_ALGORITHM = "RSA";
- // Timeout value (in milliseconds) for calls to service.
- private static final int TIMEOUT_MS = 10 * 1000;
+ // Timeout value (in milliseconds) for calls to service.
+ private static final int TIMEOUT_MS = 10 * 1000;
- private static final SecureRandom RANDOM = new SecureRandom();
- private static final boolean DEBUG_LICENSE_ERROR = false;
+ private static final SecureRandom RANDOM = new SecureRandom();
+ private static final boolean DEBUG_LICENSE_ERROR = false;
- private ILicensingService mService;
+ private ILicensingService mService;
- private PublicKey mPublicKey;
- private final Context mContext;
- private final Policy mPolicy;
- /**
+ private PublicKey mPublicKey;
+ private final Context mContext;
+ private final Policy mPolicy;
+ /**
* A handler for running tasks on a background thread. We don't want license processing to block
* the UI thread.
*/
- private Handler mHandler;
- private final String mPackageName;
- private final String mVersionCode;
- private final Set<LicenseValidator> mChecksInProgress = new HashSet<LicenseValidator>();
- private final Queue<LicenseValidator> mPendingChecks = new LinkedList<LicenseValidator>();
+ private Handler mHandler;
+ private final String mPackageName;
+ private final String mVersionCode;
+ private final Set<LicenseValidator> mChecksInProgress = new HashSet<LicenseValidator>();
+ private final Queue<LicenseValidator> mPendingChecks = new LinkedList<LicenseValidator>();
- /**
+ /**
* @param context a Context
* @param policy implementation of Policy
* @param encodedPublicKey Base64-encoded RSA public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
- public LicenseChecker(Context context, Policy policy, String encodedPublicKey) {
- mContext = context;
- mPolicy = policy;
- mPublicKey = generatePublicKey(encodedPublicKey);
- mPackageName = mContext.getPackageName();
- mVersionCode = getVersionCode(context, mPackageName);
- HandlerThread handlerThread = new HandlerThread("background thread");
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
- }
-
- /**
+ public LicenseChecker(Context context, Policy policy, String encodedPublicKey) {
+ mContext = context;
+ mPolicy = policy;
+ mPublicKey = generatePublicKey(encodedPublicKey);
+ mPackageName = mContext.getPackageName();
+ mVersionCode = getVersionCode(context, mPackageName);
+ HandlerThread handlerThread = new HandlerThread("background thread");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ }
+
+ /**
* Generates a PublicKey instance from a string containing the Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
- private static PublicKey generatePublicKey(String encodedPublicKey) {
- try {
- byte[] decodedKey = Base64.decode(encodedPublicKey);
- KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
-
- return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
- } catch (NoSuchAlgorithmException e) {
- // This won't happen in an Android-compatible environment.
- throw new RuntimeException(e);
- } catch (Base64DecoderException e) {
- Log.e(TAG, "Could not decode from Base64.");
- throw new IllegalArgumentException(e);
- } catch (InvalidKeySpecException e) {
- Log.e(TAG, "Invalid key specification.");
- throw new IllegalArgumentException(e);
- }
- }
-
- /**
+ private static PublicKey generatePublicKey(String encodedPublicKey) {
+ try {
+ byte[] decodedKey = Base64.decode(encodedPublicKey);
+ KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
+
+ return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
+ } catch (NoSuchAlgorithmException e) {
+ // This won't happen in an Android-compatible environment.
+ throw new RuntimeException(e);
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Could not decode from Base64.");
+ throw new IllegalArgumentException(e);
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG, "Invalid key specification.");
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
* Checks if the user should have access to the app. Binds the service if necessary.
* <p>
* NOTE: This call uses a trivially obfuscated string (base64-encoded). For best security, we
@@ -136,222 +136,223 @@ public class LicenseChecker implements ServiceConnection {
*
* @param callback
*/
- 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(Policy.LICENSED);
- } 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(
- new String(
- // Base64 encoded -
- // com.android.vending.licensing.ILicensingService
- // Consider encoding this in another way in your
- // code to improve security
- Base64.decode(
- "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")))
- // As of Android 5.0, implicit
- // Service Intents are no longer
- // allowed because it's not
- // possible for the user to
- // participate in disambiguating
- // them. This does mean we break
- // compatibility with Android
- // Cupcake devices with this
- // release, since setPackage was
- // added in Donut.
- .setPackage(
- new String(
- // Base64
- // encoded -
- // com.android.vending
- Base64.decode(
- "Y29tLmFuZHJvaWQudmVuZGluZw=="))),
- 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(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);
- } catch (Base64DecoderException e) {
- e.printStackTrace();
- }
- } else {
- mPendingChecks.offer(validator);
- runChecks();
- }
- }
- }
-
- /**
+ 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(Policy.LICENSED);
+ } 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(
+ new String(
+ // Base64 encoded -
+ // com.android.vending.licensing.ILicensingService
+ // Consider encoding this in another way in your
+ // code to improve security
+ Base64.decode(
+ "Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")))
+ // As of Android 5.0, implicit
+ // Service Intents are no longer
+ // allowed because it's not
+ // possible for the user to
+ // participate in disambiguating
+ // them. This does mean we break
+ // compatibility with Android
+ // Cupcake devices with this
+ // release, since setPackage was
+ // added in Donut.
+ .setPackage(
+ new String(
+ // Base64
+ // encoded -
+ // com.android.vending
+ Base64.decode(
+ "Y29tLmFuZHJvaWQudmVuZGluZw=="))),
+ 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(LicenseCheckerCallback.ERROR_MISSING_PERMISSION);
+ } catch (Base64DecoderException e) {
+ e.printStackTrace();
+ }
+ } else {
+ mPendingChecks.offer(validator);
+ runChecks();
+ }
+ }
+ }
+
+ /**
* Triggers the last deep link licensing URL returned from the server, which redirects users to a
* page which enables them to gain access to the app. If no such URL is returned by the server, it
* will go to the details page of the app in the Play Store.
*/
- public void followLastLicensingUrl(Context context) {
- String licensingUrl = mPolicy.getLicensingUrl();
- if (licensingUrl == null) {
- licensingUrl = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
- }
- Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(licensingUrl));
- context.startActivity(marketIntent);
- }
-
- 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();
- }
- }
-
- private class ResultListener extends ILicenseResultListener.Stub {
- private final LicenseValidator mValidator;
- private Runnable mOnTimeout;
-
- public ResultListener(LicenseValidator validator) {
- mValidator = validator;
- mOnTimeout = new Runnable() {
- public void run() {
- Log.i(TAG, "Check timed out.");
- handleServiceConnectionError(mValidator);
- finishCheck(mValidator);
- }
- };
- startTimeout();
- }
-
- 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;
-
- // Runs in IPC thread pool. Post it to the Handler, so we can guarantee
- // either this or the timeout runs.
- public void verifyLicense(final int responseCode, final String signedData,
- final String signature) {
- mHandler.post(new Runnable() {
- public void run() {
- Log.i(TAG, "Received response.");
- // Make sure it hasn't already timed out.
- if (mChecksInProgress.contains(mValidator)) {
- clearTimeout();
- mValidator.verify(mPublicKey, responseCode, signedData, signature);
- finishCheck(mValidator);
- }
- if (DEBUG_LICENSE_ERROR) {
- boolean logResponse;
- String stringError = null;
- switch (responseCode) {
- case ERROR_CONTACTING_SERVER:
- logResponse = true;
- stringError = "ERROR_CONTACTING_SERVER";
- break;
- case ERROR_INVALID_PACKAGE_NAME:
- logResponse = true;
- stringError = "ERROR_INVALID_PACKAGE_NAME";
- break;
- case ERROR_NON_MATCHING_UID:
- logResponse = true;
- stringError = "ERROR_NON_MATCHING_UID";
- break;
- default:
- logResponse = false;
- }
-
- if (logResponse) {
- String android_id = Secure.getString(mContext.getContentResolver(),
- Secure.ANDROID_ID);
- Date date = new Date();
- Log.d(TAG, "Server Failure: " + stringError);
- Log.d(TAG, "Android ID: " + android_id);
- Log.d(TAG, "Time: " + date.toGMTString());
- }
- }
- }
- });
- }
-
- private void startTimeout() {
- Log.i(TAG, "Start monitoring timeout.");
- mHandler.postDelayed(mOnTimeout, TIMEOUT_MS);
- }
-
- private void clearTimeout() {
- Log.i(TAG, "Clearing timeout.");
- mHandler.removeCallbacks(mOnTimeout);
- }
- }
-
- public synchronized void onServiceConnected(ComponentName name, IBinder service) {
- mService = ILicensingService.Stub.asInterface(service);
- runChecks();
- }
-
- public synchronized void onServiceDisconnected(ComponentName name) {
- // Called when the connection with the service has been
- // unexpectedly disconnected. That is, Market crashed.
- // If there are any checks in progress, the timeouts will handle them.
- Log.w(TAG, "Service unexpectedly disconnected.");
- mService = null;
- }
-
- /**
+ public void followLastLicensingUrl(Context context) {
+ String licensingUrl = mPolicy.getLicensingUrl();
+ if (licensingUrl == null) {
+ licensingUrl = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
+ }
+ Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(licensingUrl));
+ context.startActivity(marketIntent);
+ }
+
+ 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();
+ }
+ }
+
+ private class ResultListener extends ILicenseResultListener.Stub {
+ private final LicenseValidator mValidator;
+ private Runnable mOnTimeout;
+
+ public ResultListener(LicenseValidator validator) {
+ mValidator = validator;
+ mOnTimeout = new Runnable() {
+ public void run() {
+ Log.i(TAG, "Check timed out.");
+ handleServiceConnectionError(mValidator);
+ finishCheck(mValidator);
+ }
+ };
+ startTimeout();
+ }
+
+ 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;
+
+ // Runs in IPC thread pool. Post it to the Handler, so we can guarantee
+ // either this or the timeout runs.
+ public void verifyLicense(final int responseCode, final String signedData,
+ final String signature) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ Log.i(TAG, "Received response.");
+ // Make sure it hasn't already timed out.
+ if (mChecksInProgress.contains(mValidator)) {
+ clearTimeout();
+ mValidator.verify(mPublicKey, responseCode, signedData, signature);
+ finishCheck(mValidator);
+ }
+ if (DEBUG_LICENSE_ERROR) {
+ boolean logResponse;
+ String stringError = null;
+ switch (responseCode) {
+ case ERROR_CONTACTING_SERVER:
+ logResponse = true;
+ stringError = "ERROR_CONTACTING_SERVER";
+ break;
+ case ERROR_INVALID_PACKAGE_NAME:
+ logResponse = true;
+ stringError = "ERROR_INVALID_PACKAGE_NAME";
+ break;
+ case ERROR_NON_MATCHING_UID:
+ logResponse = true;
+ stringError = "ERROR_NON_MATCHING_UID";
+ break;
+ default:
+ logResponse = false;
+ }
+
+ if (logResponse) {
+ String android_id = Secure.getString(mContext.getContentResolver(),
+ Secure.ANDROID_ID);
+ Date date = new Date();
+ Log.d(TAG, "Server Failure: " + stringError);
+ Log.d(TAG, "Android ID: " + android_id);
+ Log.d(TAG, "Time: " + date.toGMTString());
+ }
+ }
+
+ }
+ });
+ }
+
+ private void startTimeout() {
+ Log.i(TAG, "Start monitoring timeout.");
+ mHandler.postDelayed(mOnTimeout, TIMEOUT_MS);
+ }
+
+ private void clearTimeout() {
+ Log.i(TAG, "Clearing timeout.");
+ mHandler.removeCallbacks(mOnTimeout);
+ }
+ }
+
+ public synchronized void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ILicensingService.Stub.asInterface(service);
+ runChecks();
+ }
+
+ public synchronized void onServiceDisconnected(ComponentName name) {
+ // Called when the connection with the service has been
+ // unexpectedly disconnected. That is, Market crashed.
+ // If there are any checks in progress, the timeouts will handle them.
+ Log.w(TAG, "Service unexpectedly disconnected.");
+ mService = null;
+ }
+
+ /**
* Generates policy response for service connection errors, as a result of disconnections or
* timeouts.
*/
- private synchronized void handleServiceConnectionError(LicenseValidator validator) {
- mPolicy.processServerResponse(Policy.RETRY, null);
-
- if (mPolicy.allowAccess()) {
- validator.getCallback().allow(Policy.RETRY);
- } else {
- validator.getCallback().dontAllow(Policy.RETRY);
- }
- }
-
- /** Unbinds service if necessary and removes reference to it. */
- private void cleanupService() {
- if (mService != null) {
- try {
- mContext.unbindService(this);
- } catch (IllegalArgumentException e) {
- // Somehow we've already been unbound. This is a non-fatal
- // error.
- Log.e(TAG, "Unable to unbind from licensing service (already unbound)");
- }
- mService = null;
- }
- }
-
- /**
+ private synchronized void handleServiceConnectionError(LicenseValidator validator) {
+ mPolicy.processServerResponse(Policy.RETRY, null);
+
+ if (mPolicy.allowAccess()) {
+ validator.getCallback().allow(Policy.RETRY);
+ } else {
+ validator.getCallback().dontAllow(Policy.RETRY);
+ }
+ }
+
+ /** Unbinds service if necessary and removes reference to it. */
+ private void cleanupService() {
+ if (mService != null) {
+ try {
+ mContext.unbindService(this);
+ } catch (IllegalArgumentException e) {
+ // Somehow we've already been unbound. This is a non-fatal
+ // error.
+ Log.e(TAG, "Unable to unbind from licensing service (already unbound)");
+ }
+ mService = null;
+ }
+ }
+
+ /**
* Inform the library that the context is about to be destroyed, so that any open connections
* can be cleaned up.
* <p>
@@ -359,30 +360,30 @@ public class LicenseChecker implements ServiceConnection {
* screen rotation if an Activity requests the license check or when the user exits the
* application.
*/
- public synchronized void onDestroy() {
- cleanupService();
- mHandler.getLooper().quit();
- }
+ public synchronized void onDestroy() {
+ cleanupService();
+ mHandler.getLooper().quit();
+ }
- /** Generates a nonce (number used once). */
- private int generateNonce() {
- return RANDOM.nextInt();
- }
+ /** 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 "";
- }
- }
+ 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/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java
index dc2c2d70bf..8b869ddaaf 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/LicenseCheckerCallback.java
@@ -34,34 +34,34 @@ package com.google.android.vending.licensing;
*/
public interface LicenseCheckerCallback {
- /**
+ /**
* Allow use. App should proceed as normal.
*
* @param reason Policy.LICENSED or Policy.RETRY typically. (although in
* theory the policy can return Policy.NOT_LICENSED here as well)
*/
- public void allow(int reason);
+ public void allow(int reason);
- /**
+ /**
* Don't allow use. App should inform user and take appropriate action.
*
* @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory
* the policy can return Policy.LICENSED here as well ---
* perhaps the call to the LVL took too long, for example)
*/
- public void dontAllow(int reason);
+ public void dontAllow(int reason);
- /** Application error codes. */
- public static final int ERROR_INVALID_PACKAGE_NAME = 1;
- public static final int ERROR_NON_MATCHING_UID = 2;
- public static final int ERROR_NOT_MARKET_MANAGED = 3;
- public static final int ERROR_CHECK_IN_PROGRESS = 4;
- public static final int ERROR_INVALID_PUBLIC_KEY = 5;
- public static final int ERROR_MISSING_PERMISSION = 6;
+ /** Application error codes. */
+ public static final int ERROR_INVALID_PACKAGE_NAME = 1;
+ public static final int ERROR_NON_MATCHING_UID = 2;
+ public static final int ERROR_NOT_MARKET_MANAGED = 3;
+ public static final int ERROR_CHECK_IN_PROGRESS = 4;
+ public static final int ERROR_INVALID_PUBLIC_KEY = 5;
+ public static final int ERROR_MISSING_PERMISSION = 6;
- /**
+ /**
* Error in application code. Caller did not call or set up license checker
* correctly. Should be considered fatal.
*/
- public void applicationError(int errorCode);
+ public void applicationError(int errorCode);
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java b/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java
index 77f7dc7295..11a00786d0 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/LicenseValidator.java
@@ -33,52 +33,52 @@ import java.security.SignatureException;
* 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_SERVER_FAILURE = 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;
- private final DeviceLimiter mDeviceLimiter;
-
- LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback,
- int nonce, String packageName, String versionCode) {
- mPolicy = policy;
- mDeviceLimiter = deviceLimiter;
- mCallback = callback;
- mNonce = nonce;
- mPackageName = packageName;
- mVersionCode = versionCode;
- }
-
- public LicenseCheckerCallback getCallback() {
- return mCallback;
- }
-
- public int getNonce() {
- return mNonce;
- }
-
- public String getPackageName() {
- return mPackageName;
- }
-
- private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
-
- /**
+ 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_SERVER_FAILURE = 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;
+ private final DeviceLimiter mDeviceLimiter;
+
+ LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback,
+ int nonce, String packageName, String versionCode) {
+ mPolicy = policy;
+ mDeviceLimiter = deviceLimiter;
+ mCallback = callback;
+ mNonce = nonce;
+ mPackageName = packageName;
+ mVersionCode = versionCode;
+ }
+
+ public LicenseCheckerCallback getCallback() {
+ return mCallback;
+ }
+
+ public int getNonce() {
+ return mNonce;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
+
+ /**
* Verifies the response from server and calls appropriate callback method.
*
* @param publicKey public key associated with the developer account
@@ -86,147 +86,146 @@ class LicenseValidator {
* @param signedData signed data from server
* @param signature server signature
*/
- public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) {
- String userId = null;
- // Skip signature check for unsuccessful requests
- ResponseData data = null;
- if (responseCode == LICENSED || responseCode == NOT_LICENSED ||
- responseCode == LICENSED_OLD_KEY) {
- // Verify signature.
- try {
- if (TextUtils.isEmpty(signedData)) {
- Log.e(TAG, "Signature verification failed: signedData is empty. "
- +
- "(Device not signed-in to any Google accounts?)");
- handleInvalidResponse();
- return;
- }
-
- Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
- sig.initVerify(publicKey);
- sig.update(signedData.getBytes());
-
- if (!sig.verify(Base64.decode(signature))) {
- Log.e(TAG, "Signature verification failed.");
- handleInvalidResponse();
- return;
- }
- } catch (NoSuchAlgorithmException e) {
- // This can't happen on an Android compatible device.
- throw new RuntimeException(e);
- } catch (InvalidKeyException e) {
- handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
- return;
- } catch (SignatureException e) {
- throw new RuntimeException(e);
- } catch (Base64DecoderException e) {
- Log.e(TAG, "Could not Base64-decode signature.");
- handleInvalidResponse();
- return;
- }
-
- // Parse and validate response.
- 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;
- }
-
- // Application-specific user identifier.
- userId = data.userId;
- if (TextUtils.isEmpty(userId)) {
- Log.e(TAG, "User identifier is empty.");
- handleInvalidResponse();
- return;
- }
- }
-
- switch (responseCode) {
- case LICENSED:
- case LICENSED_OLD_KEY:
- int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId);
- handleResponse(limiterResponse, data);
- break;
- case NOT_LICENSED:
- handleResponse(Policy.NOT_LICENSED, data);
- break;
- case ERROR_CONTACTING_SERVER:
- Log.w(TAG, "Error contacting licensing server.");
- handleResponse(Policy.RETRY, data);
- break;
- case ERROR_SERVER_FAILURE:
- Log.w(TAG, "An error has occurred on the licensing server.");
- handleResponse(Policy.RETRY, data);
- break;
- case ERROR_OVER_QUOTA:
- Log.w(TAG, "Licensing server is refusing to talk to this device, over quota.");
- handleResponse(Policy.RETRY, data);
- break;
- case ERROR_INVALID_PACKAGE_NAME:
- handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
- break;
- case ERROR_NON_MATCHING_UID:
- handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID);
- break;
- case ERROR_NOT_MARKET_MANAGED:
- handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED);
- break;
- default:
- Log.e(TAG, "Unknown response code for license check.");
- handleInvalidResponse();
- }
- }
-
- /**
+ public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) {
+ String userId = null;
+ // Skip signature check for unsuccessful requests
+ ResponseData data = null;
+ if (responseCode == LICENSED || responseCode == NOT_LICENSED ||
+ responseCode == LICENSED_OLD_KEY) {
+ // Verify signature.
+ try {
+ if (TextUtils.isEmpty(signedData)) {
+ Log.e(TAG, "Signature verification failed: signedData is empty. " +
+ "(Device not signed-in to any Google accounts?)");
+ handleInvalidResponse();
+ return;
+ }
+
+ Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
+ sig.initVerify(publicKey);
+ sig.update(signedData.getBytes());
+
+ if (!sig.verify(Base64.decode(signature))) {
+ Log.e(TAG, "Signature verification failed.");
+ handleInvalidResponse();
+ return;
+ }
+ } catch (NoSuchAlgorithmException e) {
+ // This can't happen on an Android compatible device.
+ throw new RuntimeException(e);
+ } catch (InvalidKeyException e) {
+ handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY);
+ return;
+ } catch (SignatureException e) {
+ throw new RuntimeException(e);
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Could not Base64-decode signature.");
+ handleInvalidResponse();
+ return;
+ }
+
+ // Parse and validate response.
+ 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;
+ }
+
+ // Application-specific user identifier.
+ userId = data.userId;
+ if (TextUtils.isEmpty(userId)) {
+ Log.e(TAG, "User identifier is empty.");
+ handleInvalidResponse();
+ return;
+ }
+ }
+
+ switch (responseCode) {
+ case LICENSED:
+ case LICENSED_OLD_KEY:
+ int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId);
+ handleResponse(limiterResponse, data);
+ break;
+ case NOT_LICENSED:
+ handleResponse(Policy.NOT_LICENSED, data);
+ break;
+ case ERROR_CONTACTING_SERVER:
+ Log.w(TAG, "Error contacting licensing server.");
+ handleResponse(Policy.RETRY, data);
+ break;
+ case ERROR_SERVER_FAILURE:
+ Log.w(TAG, "An error has occurred on the licensing server.");
+ handleResponse(Policy.RETRY, data);
+ break;
+ case ERROR_OVER_QUOTA:
+ Log.w(TAG, "Licensing server is refusing to talk to this device, over quota.");
+ handleResponse(Policy.RETRY, data);
+ break;
+ case ERROR_INVALID_PACKAGE_NAME:
+ handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME);
+ break;
+ case ERROR_NON_MATCHING_UID:
+ handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID);
+ break;
+ case ERROR_NOT_MARKET_MANAGED:
+ handleApplicationError(LicenseCheckerCallback.ERROR_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
* @param rawData
*/
- private void handleResponse(int response, ResponseData rawData) {
- // Update policy data and increment retry counter (if needed)
- mPolicy.processServerResponse(response, rawData);
-
- // Given everything we know, including cached data, ask the policy if we should grant
- // access.
- if (mPolicy.allowAccess()) {
- mCallback.allow(response);
- } else {
- mCallback.dontAllow(response);
- }
- }
-
- private void handleApplicationError(int code) {
- mCallback.applicationError(code);
- }
-
- private void handleInvalidResponse() {
- mCallback.dontAllow(Policy.NOT_LICENSED);
- }
+ private void handleResponse(int response, ResponseData rawData) {
+ // Update policy data and increment retry counter (if needed)
+ mPolicy.processServerResponse(response, rawData);
+
+ // Given everything we know, including cached data, ask the policy if we should grant
+ // access.
+ if (mPolicy.allowAccess()) {
+ mCallback.allow(response);
+ } else {
+ mCallback.dontAllow(response);
+ }
+ }
+
+ private void handleApplicationError(int code) {
+ mCallback.applicationError(code);
+ }
+
+ private void handleInvalidResponse() {
+ mCallback.dontAllow(Policy.NOT_LICENSED);
+ }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java b/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java
index a43e454228..d87af3153f 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/NullDeviceLimiter.java
@@ -26,7 +26,7 @@ package com.google.android.vending.licensing;
*/
public class NullDeviceLimiter implements DeviceLimiter {
- public int isDeviceAllowed(String userId) {
- return Policy.LICENSED;
- }
+ public int isDeviceAllowed(String userId) {
+ return Policy.LICENSED;
+ }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java
index 8731d03aa6..008c150a8e 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/Obfuscator.java
@@ -27,16 +27,16 @@ package com.google.android.vending.licensing;
*/
public interface Obfuscator {
- /**
+ /**
* Obfuscate a string that is being stored into shared preferences.
*
* @param original The data that is to be obfuscated.
* @param key The key for the data that is to be obfuscated.
* @return A transformed version of the original data.
*/
- String obfuscate(String original, String key);
+ String obfuscate(String original, String key);
- /**
+ /**
* Undo the transformation applied to data by the obfuscate() method.
*
* @param obfuscated The data that is to be un-obfuscated.
@@ -44,5 +44,5 @@ public interface Obfuscator {
* @return The original data transformed by the obfuscate() method.
* @throws ValidationException Optionally thrown if a data integrity check fails.
*/
- String unobfuscate(String obfuscated, String key) throws ValidationException;
+ String unobfuscate(String obfuscated, String key) throws ValidationException;
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/Policy.java b/platform/android/java/src/com/google/android/vending/licensing/Policy.java
index 65202aceb9..b672a078b7 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/Policy.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/Policy.java
@@ -22,27 +22,27 @@ package com.google.android.vending.licensing;
*/
public interface Policy {
- /**
+ /**
* Change these values to make it more difficult for tools to automatically
* strip LVL protection from your APK.
*/
- /**
+ /**
* LICENSED means that the server returned back a valid license response
*/
- public static final int LICENSED = 0x0100;
- /**
+ public static final int LICENSED = 0x0100;
+ /**
* NOT_LICENSED means that the server returned back a valid license response
* that indicated that the user definitively is not licensed
*/
- public static final int NOT_LICENSED = 0x0231;
- /**
+ public static final int NOT_LICENSED = 0x0231;
+ /**
* RETRY means that the license response was unable to be determined ---
* perhaps as a result of faulty networking
*/
- public static final int RETRY = 0x0123;
+ public static final int RETRY = 0x0123;
- /**
+ /**
* Provide results from contact with the license server. Retry counts are
* incremented if the current value of response is RETRY. Results will be
* used for any future policy decisions.
@@ -50,16 +50,16 @@ public interface Policy {
* @param response the result from validating the server response
* @param rawData the raw server response data, can be null for RETRY
*/
- void processServerResponse(int response, ResponseData rawData);
+ void processServerResponse(int response, ResponseData rawData);
- /**
+ /**
* Check if the user should be allowed access to the application.
*/
- boolean allowAccess();
+ boolean allowAccess();
- /**
+ /**
* Gets the licensing URL returned by the server that can enable access for unlicensed apps (e.g.
* buy app on the Play Store).
*/
- String getLicensingUrl();
+ String getLicensingUrl();
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java b/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java
index 099bb1c48b..7c42bfc28a 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/PreferenceObfuscator.java
@@ -24,55 +24,54 @@ import android.util.Log;
*/
public class PreferenceObfuscator {
- private static final String TAG = "PreferenceObfuscator";
+ private static final String TAG = "PreferenceObfuscator";
- private final SharedPreferences mPreferences;
- private final Obfuscator mObfuscator;
- private SharedPreferences.Editor mEditor;
+ private final SharedPreferences mPreferences;
+ private final Obfuscator mObfuscator;
+ private SharedPreferences.Editor mEditor;
- /**
+ /**
* Constructor.
*
* @param sp A SharedPreferences instance provided by the system.
* @param o The Obfuscator to use when reading or writing data.
*/
- public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) {
- mPreferences = sp;
- mObfuscator = o;
- mEditor = null;
- }
+ public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) {
+ mPreferences = sp;
+ mObfuscator = o;
+ mEditor = null;
+ }
- public void putString(String key, String value) {
- if (mEditor == null) {
- mEditor = mPreferences.edit();
- mEditor.apply();
- }
- String obfuscatedValue = mObfuscator.obfuscate(value, key);
- mEditor.putString(key, obfuscatedValue);
- }
+ public void putString(String key, String value) {
+ if (mEditor == null) {
+ mEditor = mPreferences.edit();
+ }
+ String obfuscatedValue = mObfuscator.obfuscate(value, key);
+ mEditor.putString(key, obfuscatedValue);
+ }
- public String getString(String key, String defValue) {
- String result;
- String value = mPreferences.getString(key, null);
- if (value != null) {
- try {
- result = mObfuscator.unobfuscate(value, key);
- } catch (ValidationException e) {
- // Unable to unobfuscate, data corrupt or tampered
- Log.w(TAG, "Validation error while reading preference: " + key);
- result = defValue;
- }
- } else {
- // Preference not found
- result = defValue;
- }
- return result;
- }
+ public String getString(String key, String defValue) {
+ String result;
+ String value = mPreferences.getString(key, null);
+ if (value != null) {
+ try {
+ result = mObfuscator.unobfuscate(value, key);
+ } catch (ValidationException e) {
+ // Unable to unobfuscate, data corrupt or tampered
+ Log.w(TAG, "Validation error while reading preference: " + key);
+ result = defValue;
+ }
+ } else {
+ // Preference not found
+ result = defValue;
+ }
+ return result;
+ }
- public void commit() {
- if (mEditor != null) {
- mEditor.commit();
- mEditor = null;
- }
- }
+ public void commit() {
+ if (mEditor != null) {
+ mEditor.commit();
+ mEditor = null;
+ }
+ }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java b/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java
index 1c802f8e45..3b5d557e76 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/ResponseData.java
@@ -25,56 +25,57 @@ import java.util.regex.Pattern;
*/
public 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;
+ 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.
- int index = responseData.indexOf(':');
- String mainData, extraData;
- if (-1 == index) {
- mainData = responseData;
- extraData = "";
- } else {
- mainData = responseData.substring(0, index);
- extraData = index >= responseData.length() ? "" : responseData.substring(index + 1);
- }
+ public static ResponseData parse(String responseData) {
+ // Must parse out main response data and response-specific data.
+ int index = responseData.indexOf(':');
+ String mainData, extraData;
+ if (-1 == index) {
+ mainData = responseData;
+ extraData = "";
+ } else {
+ mainData = responseData.substring(0, index);
+ extraData = index >= responseData.length() ? "" : responseData.substring(index + 1);
+ }
- String[] fields = TextUtils.split(mainData, Pattern.quote("|"));
- if (fields.length < 6) {
- throw new IllegalArgumentException("Wrong number of fields.");
- }
+ String[] fields = TextUtils.split(mainData, Pattern.quote("|"));
+ if (fields.length < 6) {
+ 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];
- // Application-specific user identifier.
- data.userId = fields[4];
- data.timestamp = Long.parseLong(fields[5]);
+ 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];
+ // Application-specific user identifier.
+ data.userId = fields[4];
+ data.timestamp = Long.parseLong(fields[5]);
- return data;
- }
+ return data;
+ }
- @Override
- public String toString() {
- return TextUtils.join("|", new Object[] {
- responseCode, nonce, packageName, versionCode,
- userId, timestamp });
- }
+ @Override
+ public String toString() {
+ return TextUtils.join("|", new Object[] {
+ responseCode, nonce, packageName, versionCode,
+ userId, timestamp
+ });
+ }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java
index b9a50c1104..e2f0bfdca8 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/ServerManagedPolicy.java
@@ -43,49 +43,49 @@ import com.google.android.vending.licensing.util.URIQueryDecoder;
*/
public class ServerManagedPolicy implements Policy {
- private static final String TAG = "ServerManagedPolicy";
- private static final String PREFS_FILE = "com.google.android.vending.licensing.ServerManagedPolicy";
- private static final String PREF_LAST_RESPONSE = "lastResponse";
- private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
- private static final String PREF_RETRY_UNTIL = "retryUntil";
- private static final String PREF_MAX_RETRIES = "maxRetries";
- private static final String PREF_RETRY_COUNT = "retryCount";
- private static final String PREF_LICENSING_URL = "licensingUrl";
- private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
- private static final String DEFAULT_RETRY_UNTIL = "0";
- private static final String DEFAULT_MAX_RETRIES = "0";
- private static final String DEFAULT_RETRY_COUNT = "0";
+ private static final String TAG = "ServerManagedPolicy";
+ private static final String PREFS_FILE = "com.google.android.vending.licensing.ServerManagedPolicy";
+ private static final String PREF_LAST_RESPONSE = "lastResponse";
+ private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp";
+ private static final String PREF_RETRY_UNTIL = "retryUntil";
+ private static final String PREF_MAX_RETRIES = "maxRetries";
+ private static final String PREF_RETRY_COUNT = "retryCount";
+ private static final String PREF_LICENSING_URL = "licensingUrl";
+ private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
+ private static final String DEFAULT_RETRY_UNTIL = "0";
+ private static final String DEFAULT_MAX_RETRIES = "0";
+ private static final String DEFAULT_RETRY_COUNT = "0";
- private static final long MILLIS_PER_MINUTE = 60 * 1000;
+ private static final long MILLIS_PER_MINUTE = 60 * 1000;
- private long mValidityTimestamp;
- private long mRetryUntil;
- private long mMaxRetries;
- private long mRetryCount;
- private long mLastResponseTime = 0;
- private int mLastResponse;
- private String mLicensingUrl;
- private PreferenceObfuscator mPreferences;
+ private long mValidityTimestamp;
+ private long mRetryUntil;
+ private long mMaxRetries;
+ private long mRetryCount;
+ private long mLastResponseTime = 0;
+ private int mLastResponse;
+ private String mLicensingUrl;
+ private PreferenceObfuscator mPreferences;
- /**
+ /**
* @param context The context for the current application
* @param obfuscator An obfuscator to be used with preferences.
*/
- public ServerManagedPolicy(Context context, Obfuscator obfuscator) {
- // Import old values
- SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
- mPreferences = new PreferenceObfuscator(sp, obfuscator);
- mLastResponse = Integer.parseInt(
- mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
- mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
- DEFAULT_VALIDITY_TIMESTAMP));
- mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
- mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
- mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
- mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null);
- }
+ public ServerManagedPolicy(Context context, Obfuscator obfuscator) {
+ // Import old values
+ SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
+ mPreferences = new PreferenceObfuscator(sp, obfuscator);
+ mLastResponse = Integer.parseInt(
+ mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)));
+ mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP,
+ DEFAULT_VALIDITY_TIMESTAMP));
+ mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
+ mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
+ mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
+ mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null);
+ }
- /**
+ /**
* Process a new response from the license server.
* <p>
* This data will be used for computing future policy decisions. The
@@ -102,159 +102,159 @@ public class ServerManagedPolicy implements Policy {
* @param response the result from validating the server response
* @param rawData the raw server response data
*/
- public void processServerResponse(int response, ResponseData rawData) {
+ public void processServerResponse(int response, ResponseData rawData) {
- // Update retry counter
- if (response != Policy.RETRY) {
- setRetryCount(0);
- } else {
- setRetryCount(mRetryCount + 1);
- }
+ // Update retry counter
+ if (response != Policy.RETRY) {
+ setRetryCount(0);
+ } else {
+ setRetryCount(mRetryCount + 1);
+ }
- // Update server policy data
- Map<String, String> extras = decodeExtras(rawData);
- if (response == Policy.LICENSED) {
- mLastResponse = response;
- // Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
- setLicensingUrl(null);
- setValidityTimestamp(extras.get("VT"));
- setRetryUntil(extras.get("GT"));
- setMaxRetries(extras.get("GR"));
- } else if (response == Policy.NOT_LICENSED) {
- // Clear out stale retry params
- setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
- setRetryUntil(DEFAULT_RETRY_UNTIL);
- setMaxRetries(DEFAULT_MAX_RETRIES);
- // Update the licensing URL
- setLicensingUrl(extras.get("LU"));
- }
+ // Update server policy data
+ Map<String, String> extras = decodeExtras(rawData);
+ if (response == Policy.LICENSED) {
+ mLastResponse = response;
+ // Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
+ setLicensingUrl(null);
+ setValidityTimestamp(extras.get("VT"));
+ setRetryUntil(extras.get("GT"));
+ setMaxRetries(extras.get("GR"));
+ } else if (response == Policy.NOT_LICENSED) {
+ // Clear out stale retry params
+ setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
+ setRetryUntil(DEFAULT_RETRY_UNTIL);
+ setMaxRetries(DEFAULT_MAX_RETRIES);
+ // Update the licensing URL
+ setLicensingUrl(extras.get("LU"));
+ }
- setLastResponse(response);
- mPreferences.commit();
- }
+ setLastResponse(response);
+ mPreferences.commit();
+ }
- /**
+ /**
* Set the last license response received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param l the response
*/
- private void setLastResponse(int l) {
- mLastResponseTime = System.currentTimeMillis();
- mLastResponse = l;
- mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l));
- }
+ private void setLastResponse(int l) {
+ mLastResponseTime = System.currentTimeMillis();
+ mLastResponse = l;
+ mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l));
+ }
- /**
+ /**
* Set the current retry count and add to preferences. You must manually
* call PreferenceObfuscator.commit() to commit these changes to disk.
*
* @param c the new retry count
*/
- private void setRetryCount(long c) {
- mRetryCount = c;
- mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c));
- }
+ private void setRetryCount(long c) {
+ mRetryCount = c;
+ mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c));
+ }
- public long getRetryCount() {
- return mRetryCount;
- }
+ public long getRetryCount() {
+ return mRetryCount;
+ }
- /**
+ /**
* Set the last validity timestamp (VT) received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param validityTimestamp the VT string received
*/
- private void setValidityTimestamp(String validityTimestamp) {
- Long lValidityTimestamp;
- try {
- lValidityTimestamp = Long.parseLong(validityTimestamp);
- } catch (NumberFormatException e) {
- // No response or not parsable, expire in one minute.
- Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute");
- lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE;
- validityTimestamp = Long.toString(lValidityTimestamp);
- }
+ private void setValidityTimestamp(String validityTimestamp) {
+ Long lValidityTimestamp;
+ try {
+ lValidityTimestamp = Long.parseLong(validityTimestamp);
+ } catch (NumberFormatException e) {
+ // No response or not parsable, expire in one minute.
+ Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute");
+ lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE;
+ validityTimestamp = Long.toString(lValidityTimestamp);
+ }
- mValidityTimestamp = lValidityTimestamp;
- mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp);
- }
+ mValidityTimestamp = lValidityTimestamp;
+ mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp);
+ }
- public long getValidityTimestamp() {
- return mValidityTimestamp;
- }
+ public long getValidityTimestamp() {
+ return mValidityTimestamp;
+ }
- /**
+ /**
* Set the retry until timestamp (GT) received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param retryUntil the GT string received
*/
- private void setRetryUntil(String retryUntil) {
- Long lRetryUntil;
- try {
- lRetryUntil = Long.parseLong(retryUntil);
- } catch (NumberFormatException e) {
- // No response or not parsable, expire immediately
- Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled");
- retryUntil = "0";
- lRetryUntil = 0l;
- }
+ private void setRetryUntil(String retryUntil) {
+ Long lRetryUntil;
+ try {
+ lRetryUntil = Long.parseLong(retryUntil);
+ } catch (NumberFormatException e) {
+ // No response or not parsable, expire immediately
+ Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled");
+ retryUntil = "0";
+ lRetryUntil = 0l;
+ }
- mRetryUntil = lRetryUntil;
- mPreferences.putString(PREF_RETRY_UNTIL, retryUntil);
- }
+ mRetryUntil = lRetryUntil;
+ mPreferences.putString(PREF_RETRY_UNTIL, retryUntil);
+ }
- public long getRetryUntil() {
- return mRetryUntil;
- }
+ public long getRetryUntil() {
+ return mRetryUntil;
+ }
- /**
+ /**
* Set the max retries value (GR) as received from the server and add to
* preferences. You must manually call PreferenceObfuscator.commit() to
* commit these changes to disk.
*
* @param maxRetries the GR string received
*/
- private void setMaxRetries(String maxRetries) {
- Long lMaxRetries;
- try {
- lMaxRetries = Long.parseLong(maxRetries);
- } catch (NumberFormatException e) {
- // No response or not parsable, expire immediately
- Log.w(TAG, "Licence retry count (GR) missing, grace period disabled");
- maxRetries = "0";
- lMaxRetries = 0l;
- }
+ private void setMaxRetries(String maxRetries) {
+ Long lMaxRetries;
+ try {
+ lMaxRetries = Long.parseLong(maxRetries);
+ } catch (NumberFormatException e) {
+ // No response or not parsable, expire immediately
+ Log.w(TAG, "Licence retry count (GR) missing, grace period disabled");
+ maxRetries = "0";
+ lMaxRetries = 0l;
+ }
- mMaxRetries = lMaxRetries;
- mPreferences.putString(PREF_MAX_RETRIES, maxRetries);
- }
+ mMaxRetries = lMaxRetries;
+ mPreferences.putString(PREF_MAX_RETRIES, maxRetries);
+ }
- public long getMaxRetries() {
- return mMaxRetries;
- }
+ public long getMaxRetries() {
+ return mMaxRetries;
+ }
- /**
+ /**
* Set the license URL value (LU) as received from the server and add to preferences. You must
* manually call PreferenceObfuscator.commit() to commit these changes to disk.
*
* @param url the LU string received
*/
- private void setLicensingUrl(String url) {
- mLicensingUrl = url;
- mPreferences.putString(PREF_LICENSING_URL, url);
- }
+ private void setLicensingUrl(String url) {
+ mLicensingUrl = url;
+ mPreferences.putString(PREF_LICENSING_URL, url);
+ }
- public String getLicensingUrl() {
- return mLicensingUrl;
- }
+ public String getLicensingUrl() {
+ return mLicensingUrl;
+ }
- /**
+ /**
* {@inheritDoc}
*
* This implementation allows access if either:<br>
@@ -264,36 +264,37 @@ public class ServerManagedPolicy implements Policy {
* the RETRY count or in the RETRY period.
* </ol>
*/
- public boolean allowAccess() {
- long ts = System.currentTimeMillis();
- if (mLastResponse == Policy.LICENSED) {
- // Check if the LICENSED response occurred within the validity timeout.
- if (ts <= mValidityTimestamp) {
- // Cached LICENSED response is still valid.
- return true;
- }
- } else if (mLastResponse == Policy.RETRY &&
- ts < mLastResponseTime + MILLIS_PER_MINUTE) {
- // Only allow access if we are within the retry period or we haven't used up our
- // max retries.
- return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
- }
- return false;
- }
+ public boolean allowAccess() {
+ long ts = System.currentTimeMillis();
+ if (mLastResponse == Policy.LICENSED) {
+ // Check if the LICENSED response occurred within the validity timeout.
+ if (ts <= mValidityTimestamp) {
+ // Cached LICENSED response is still valid.
+ return true;
+ }
+ } else if (mLastResponse == Policy.RETRY &&
+ ts < mLastResponseTime + MILLIS_PER_MINUTE) {
+ // Only allow access if we are within the retry period or we haven't used up our
+ // max retries.
+ return (ts <= mRetryUntil || mRetryCount <= mMaxRetries);
+ }
+ return false;
+ }
- private Map<String, String> decodeExtras(
- com.google.android.vending.licensing.ResponseData rawData) {
- Map<String, String> results = new HashMap<String, String>();
- if (rawData == null) {
- return results;
- }
+ private Map<String, String> decodeExtras(
+ com.google.android.vending.licensing.ResponseData rawData) {
+ Map<String, String> results = new HashMap<String, String>();
+ if (rawData == null) {
+ return results;
+ }
+
+ try {
+ URI rawExtras = new URI("?" + rawData.extra);
+ URIQueryDecoder.DecodeQuery(rawExtras, results);
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
+ }
+ return results;
+ }
- try {
- URI rawExtras = new URI("?" + rawData.extra);
- URIQueryDecoder.DecodeQuery(rawExtras, results);
- } catch (URISyntaxException e) {
- Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
- }
- return results;
- }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java b/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java
index 9849730c38..c2d55c37f1 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/StrictPolicy.java
@@ -38,18 +38,18 @@ import java.util.Map;
*/
public class StrictPolicy implements Policy {
- private static final String TAG = "StrictPolicy";
+ private static final String TAG = "StrictPolicy";
- private int mLastResponse;
- private String mLicensingUrl;
+ private int mLastResponse;
+ private String mLicensingUrl;
- public StrictPolicy() {
- // Set default policy. This will force the application to check the policy on launch.
- mLastResponse = Policy.RETRY;
- mLicensingUrl = null;
- }
+ public StrictPolicy() {
+ // Set default policy. This will force the application to check the policy on launch.
+ mLastResponse = Policy.RETRY;
+ mLicensingUrl = null;
+ }
- /**
+ /**
* Process a new response from the license server. Since we aren't
* performing any caching, this equates to reading the LicenseResponse.
* Any cache-related ResponseData is ignored, but the licensing URL
@@ -58,42 +58,43 @@ public class StrictPolicy implements Policy {
* @param response the result from validating the server response
* @param rawData the raw server response data
*/
- public void processServerResponse(int response, ResponseData rawData) {
- mLastResponse = response;
+ public void processServerResponse(int response, ResponseData rawData) {
+ mLastResponse = response;
- if (response == Policy.NOT_LICENSED) {
- Map<String, String> extras = decodeExtras(rawData);
- mLicensingUrl = extras.get("LU");
- }
- }
+ if (response == Policy.NOT_LICENSED) {
+ Map<String, String> extras = decodeExtras(rawData);
+ mLicensingUrl = extras.get("LU");
+ }
+ }
- /**
+ /**
* {@inheritDoc}
*
* This implementation allows access if and only if a LICENSED response
* was received the last time the server was contacted.
*/
- public boolean allowAccess() {
- return (mLastResponse == Policy.LICENSED);
- }
+ public boolean allowAccess() {
+ return (mLastResponse == Policy.LICENSED);
+ }
- public String getLicensingUrl() {
- return mLicensingUrl;
- }
+ public String getLicensingUrl() {
+ return mLicensingUrl;
+ }
- private Map<String, String> decodeExtras(
- com.google.android.vending.licensing.ResponseData rawData) {
- Map<String, String> results = new HashMap<String, String>();
- if (rawData == null) {
- return results;
- }
+ private Map<String, String> decodeExtras(
+ com.google.android.vending.licensing.ResponseData rawData) {
+ Map<String, String> results = new HashMap<String, String>();
+ if (rawData == null) {
+ return results;
+ }
+
+ try {
+ URI rawExtras = new URI("?" + rawData.extra);
+ URIQueryDecoder.DecodeQuery(rawExtras, results);
+ } catch (URISyntaxException e) {
+ Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
+ }
+ return results;
+ }
- try {
- URI rawExtras = new URI("?" + rawData.extra);
- URIQueryDecoder.DecodeQuery(rawExtras, results);
- } catch (URISyntaxException e) {
- Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
- }
- return results;
- }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java b/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java
index 79b70e6804..ee4df47c68 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/ValidationException.java
@@ -21,13 +21,13 @@ package com.google.android.vending.licensing;
* {@link Obfuscator}.}
*/
public class ValidationException extends Exception {
- public ValidationException() {
- super();
- }
+ public ValidationException() {
+ super();
+ }
- public ValidationException(String s) {
- super(s);
- }
+ public ValidationException(String s) {
+ super(s);
+ }
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java b/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java
index bd711aadf5..a0d2779af2 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/util/Base64.java
@@ -31,8 +31,6 @@ package com.google.android.vending.licensing.util;
* @version 1.3
*/
-import com.godot.game.BuildConfig;
-
/**
* Base64 converter class. This code is not a full-blown MIME encoder;
* it simply converts binary data to base64 data and back.
@@ -41,79 +39,80 @@ import com.godot.game.BuildConfig;
* class.
*/
public class Base64 {
- /** Specify encoding (value is {@code true}). */
- public final static boolean ENCODE = true;
+ /** Specify encoding (value is {@code true}). */
+ public final static boolean ENCODE = true;
- /** Specify decoding (value is {@code false}). */
- public final static boolean DECODE = false;
+ /** Specify decoding (value is {@code false}). */
+ public final static boolean DECODE = false;
- /** The equals sign (=) as a byte. */
- private final static byte EQUALS_SIGN = (byte)'=';
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte) '=';
- /** The new line character (\n) as a byte. */
- private final static byte NEW_LINE = (byte)'\n';
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte) '\n';
- /**
+ /**
* The 64 valid Base64 values.
*/
- private final static byte[] ALPHABET = { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
- (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K',
- (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P',
- (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
- (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
- (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e',
- (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j',
- (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o',
- (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t',
- (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y',
- (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3',
- (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
- (byte)'9', (byte)'+', (byte)'/' };
-
- /**
+ private final static byte[] ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '+', (byte) '/'};
+
+ /**
* The 64 valid web safe Base64 values.
*/
- private final static byte[] WEBSAFE_ALPHABET = { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
- (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K',
- (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P',
- (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
- (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
- (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e',
- (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j',
- (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o',
- (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t',
- (byte)'u', (byte)'v', (byte)'w', (byte)'x', (byte)'y',
- (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3',
- (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
- (byte)'9', (byte)'-', (byte)'_' };
-
- /**
+ private final static byte[] WEBSAFE_ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '-', (byte) '_'};
+
+ /**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
**/
- private final static byte[] DECODABET = {
- -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
- -5, -5, // Whitespace: Tab and Linefeed
- -9, -9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
- -9, -9, -9, -9, -9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
- 62, // Plus sign at decimal 43
- -9, -9, -9, // Decimal 44 - 46
- 63, // Slash at decimal 47
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
- -9, -9, -9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9, -9, -9, // Decimal 62 - 64
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
- -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
- 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
- -9, -9, -9, -9, -9 // Decimal 123 - 127
- /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9, -9, -9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
@@ -123,33 +122,33 @@ public class Base64 {
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
- };
-
- /** The web safe decodabet */
- private final static byte[] WEBSAFE_DECODABET = {
- -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
- -5, -5, // Whitespace: Tab and Linefeed
- -9, -9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
- -9, -9, -9, -9, -9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
- 62, // Dash '-' sign at decimal 45
- -9, -9, // Decimal 46-47
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
- -9, -9, -9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9, -9, -9, // Decimal 62 - 64
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
- -9, -9, -9, -9, // Decimal 91-94
- 63, // Underscore '_' at decimal 95
- -9, // Decimal 96
- 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
- -9, -9, -9, -9, -9 // Decimal 123 - 127
- /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ };
+
+ /** The web safe decodabet */
+ private final static byte[] WEBSAFE_DECODABET =
+ {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
+ 62, // Dash '-' sign at decimal 45
+ -9, -9, // Decimal 46-47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, // Decimal 91-94
+ 63, // Underscore '_' at decimal 95
+ -9, // Decimal 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
@@ -159,20 +158,20 @@ public class Base64 {
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
- };
+ };
- // Indicates white space in encoding
- private final static byte WHITE_SPACE_ENC = -5;
- // Indicates equals sign in encoding
- private final static byte EQUALS_SIGN_ENC = -1;
+ // Indicates white space in encoding
+ private final static byte WHITE_SPACE_ENC = -5;
+ // Indicates equals sign in encoding
+ private final static byte EQUALS_SIGN_ENC = -1;
- /** Defeats instantiation. */
- private Base64() {
- }
+ /** Defeats instantiation. */
+ private Base64() {
+ }
- /* ******** E N C O D I N G M E T H O D S ******** */
+ /* ******** E N C O D I N G M E T H O D S ******** */
- /**
+ /**
* Encodes up to three bytes of the array <var>source</var>
* and writes the resulting four Base64 bytes to <var>destination</var>.
* The source and destination arrays can be manipulated
@@ -194,47 +193,49 @@ public class Base64 {
* @return the <var>destination</var> array
* @since 1.3
*/
- private static byte[] encode3to4(byte[] source, int srcOffset,
- int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
- // 1 2 3
- // 01234567890123456789012345678901 Bit position
- // --------000000001111111122222222 Array position from threeBytes
- // --------| || || || | Six bit groups to index alphabet
- // >>18 >>12 >> 6 >> 0 Right shift necessary
- // 0x3f 0x3f 0x3f Additional AND
-
- // Create buffer with zero-padding if there are only one or two
- // significant bytes passed in the array.
- // We have to shift left 24 in order to flush out the 1's that appear
- // when Java treats a value as negative that is cast from a byte to an int.
- int inBuff =
- (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
-
- switch (numSigBytes) {
- case 3:
- destination[destOffset] = alphabet[(inBuff >>> 18)];
- destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
- destination[destOffset + 3] = alphabet[(inBuff)&0x3f];
- return destination;
- case 2:
- destination[destOffset] = alphabet[(inBuff >>> 18)];
- destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
- destination[destOffset + 3] = EQUALS_SIGN;
- return destination;
- case 1:
- destination[destOffset] = alphabet[(inBuff >>> 18)];
- destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- destination[destOffset + 2] = EQUALS_SIGN;
- destination[destOffset + 3] = EQUALS_SIGN;
- return destination;
- default:
- return destination;
- } // end switch
- } // end encode3to4
-
- /**
+ private static byte[] encode3to4(byte[] source, int srcOffset,
+ int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index alphabet
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff =
+ (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
+ | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
+ | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
+
+ switch (numSigBytes) {
+ case 3:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
+ return destination;
+ case 2:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ case 1:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = EQUALS_SIGN;
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+ /**
* Encodes a byte array into Base64 notation.
* Equivalent to calling
* {@code encodeBytes(source, 0, source.length)}
@@ -242,22 +243,22 @@ public class Base64 {
* @param source The data to convert
* @since 1.4
*/
- public static String encode(byte[] source) {
- return encode(source, 0, source.length, ALPHABET, true);
- }
+ public static String encode(byte[] source) {
+ return encode(source, 0, source.length, ALPHABET, true);
+ }
- /**
+ /**
* Encodes a byte array into web safe Base64 notation.
*
* @param source The data to convert
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
*/
- public static String encodeWebSafe(byte[] source, boolean doPadding) {
- return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
- }
+ public static String encodeWebSafe(byte[] source, boolean doPadding) {
+ return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
+ }
- /**
+ /**
* Encodes a byte array into Base64 notation.
*
* @param source The data to convert
@@ -268,24 +269,24 @@ public class Base64 {
* if it does not fall on 3 byte boundaries
* @since 1.4
*/
- public static String encode(byte[] source, int off, int len, byte[] alphabet,
- boolean doPadding) {
- byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
- int outLen = outBuff.length;
-
- // If doPadding is false, set length to truncate '='
- // padding characters
- while (doPadding == false && outLen > 0) {
- if (outBuff[outLen - 1] != '=') {
- break;
- }
- outLen -= 1;
- }
-
- return new String(outBuff, 0, outLen);
- }
-
- /**
+ public static String encode(byte[] source, int off, int len, byte[] alphabet,
+ boolean doPadding) {
+ byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
+ int outLen = outBuff.length;
+
+ // If doPadding is false, set length to truncate '='
+ // padding characters
+ while (doPadding == false && outLen > 0) {
+ if (outBuff[outLen - 1] != '=') {
+ break;
+ }
+ outLen -= 1;
+ }
+
+ return new String(outBuff, 0, outLen);
+ }
+
+ /**
* Encodes a byte array into Base64 notation.
*
* @param source The data to convert
@@ -295,57 +296,60 @@ public class Base64 {
* @param maxLineLength maximum length of one line.
* @return the BASE64-encoded byte array
*/
- public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
- int maxLineLength) {
- int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
- int len43 = lenDiv3 * 4;
- byte[] outBuff = new byte[len43 // Main 4:3
- + (len43 / maxLineLength)]; // New lines
-
- int d = 0;
- int e = 0;
- int len2 = len - 2;
- int lineLength = 0;
- for (; d < len2; d += 3, e += 4) {
-
- // The following block of code is the same as
- // encode3to4( source, d + off, 3, outBuff, e, alphabet );
- // but inlined for faster encoding (~20% improvement)
- int inBuff =
- ((source[d + off] << 24) >>> 8) | ((source[d + 1 + off] << 24) >>> 16) | ((source[d + 2 + off] << 24) >>> 24);
- outBuff[e] = alphabet[(inBuff >>> 18)];
- outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
- outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
- outBuff[e + 3] = alphabet[(inBuff)&0x3f];
-
- lineLength += 4;
- if (lineLength == maxLineLength) {
- outBuff[e + 4] = NEW_LINE;
- e++;
- lineLength = 0;
- } // end if: end of line
- } // end for: each piece of array
-
- if (d < len) {
- encode3to4(source, d + off, len - d, outBuff, e, alphabet);
-
- lineLength += 4;
- if (lineLength == maxLineLength) {
- // Add a last newline
- outBuff[e + 4] = NEW_LINE;
- e++;
- }
- e += 4;
- }
-
- if (BuildConfig.DEBUG && e != outBuff.length)
- throw new RuntimeException();
- return outBuff;
- }
-
- /* ******** D E C O D I N G M E T H O D S ******** */
-
- /**
+ public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
+ int maxLineLength) {
+ int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
+ int len43 = lenDiv3 * 4;
+ byte[] outBuff = new byte[len43 // Main 4:3
+ + (len43 / maxLineLength)]; // New lines
+
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for (; d < len2; d += 3, e += 4) {
+
+ // The following block of code is the same as
+ // encode3to4( source, d + off, 3, outBuff, e, alphabet );
+ // but inlined for faster encoding (~20% improvement)
+ int inBuff =
+ ((source[d + off] << 24) >>> 8)
+ | ((source[d + 1 + off] << 24) >>> 16)
+ | ((source[d + 2 + off] << 24) >>> 24);
+ outBuff[e] = alphabet[(inBuff >>> 18)];
+ outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // end for: each piece of array
+
+ if (d < len) {
+ encode3to4(source, d + off, len - d, outBuff, e, alphabet);
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ // Add a last newline
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ }
+ e += 4;
+ }
+
+ assert (e == outBuff.length);
+ return outBuff;
+ }
+
+
+ /* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
* Decodes four bytes from array <var>source</var>
* and writes the resulting bytes (up to three of them)
* to <var>destination</var>.
@@ -368,60 +372,67 @@ public class Base64 {
* @return the number of decoded bytes converted
* @since 1.3
*/
- private static int decode4to3(byte[] source, int srcOffset,
- byte[] destination, int destOffset, byte[] decodabet) {
- // Example: Dk==
- if (source[srcOffset + 2] == EQUALS_SIGN) {
- int outBuff =
- ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
-
- destination[destOffset] = (byte)(outBuff >>> 16);
- return 1;
- } else if (source[srcOffset + 3] == EQUALS_SIGN) {
- // Example: DkL=
- int outBuff =
- ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) | ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
-
- destination[destOffset] = (byte)(outBuff >>> 16);
- destination[destOffset + 1] = (byte)(outBuff >>> 8);
- return 2;
- } else {
- // Example: DkLE
- int outBuff =
- ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
-
- destination[destOffset] = (byte)(outBuff >> 16);
- destination[destOffset + 1] = (byte)(outBuff >> 8);
- destination[destOffset + 2] = (byte)(outBuff);
- return 3;
- }
- } // end decodeToBytes
-
- /**
+ private static int decode4to3(byte[] source, int srcOffset,
+ byte[] destination, int destOffset, byte[] decodabet) {
+ // Example: Dk==
+ if (source[srcOffset + 2] == EQUALS_SIGN) {
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ return 1;
+ } else if (source[srcOffset + 3] == EQUALS_SIGN) {
+ // Example: DkL=
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ destination[destOffset + 1] = (byte) (outBuff >>> 8);
+ return 2;
+ } else {
+ // Example: DkLE
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
+ | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
+
+ destination[destOffset] = (byte) (outBuff >> 16);
+ destination[destOffset + 1] = (byte) (outBuff >> 8);
+ destination[destOffset + 2] = (byte) (outBuff);
+ return 3;
+ }
+ } // end decodeToBytes
+
+
+ /**
* Decodes data from Base64 notation.
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
* @since 1.4
*/
- public static byte[] decode(String s) throws Base64DecoderException {
- byte[] bytes = s.getBytes();
- return decode(bytes, 0, bytes.length);
- }
+ public static byte[] decode(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decode(bytes, 0, bytes.length);
+ }
- /**
+ /**
* Decodes data from web safe Base64 notation.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
*/
- public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
- byte[] bytes = s.getBytes();
- return decodeWebSafe(bytes, 0, bytes.length);
- }
+ public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decodeWebSafe(bytes, 0, bytes.length);
+ }
- /**
+ /**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
@@ -430,11 +441,11 @@ public class Base64 {
* @since 1.3
* @throws Base64DecoderException
*/
- public static byte[] decode(byte[] source) throws Base64DecoderException {
- return decode(source, 0, source.length);
- }
+ public static byte[] decode(byte[] source) throws Base64DecoderException {
+ return decode(source, 0, source.length);
+ }
- /**
+ /**
* Decodes web safe Base64 content in byte array format and returns
* the decoded data.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
@@ -442,12 +453,12 @@ public class Base64 {
* @param source the string to decode (decoded in default encoding)
* @return the decoded data
*/
- public static byte[] decodeWebSafe(byte[] source)
- throws Base64DecoderException {
- return decodeWebSafe(source, 0, source.length);
- }
+ public static byte[] decodeWebSafe(byte[] source)
+ throws Base64DecoderException {
+ return decodeWebSafe(source, 0, source.length);
+ }
- /**
+ /**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
@@ -458,12 +469,12 @@ public class Base64 {
* @since 1.3
* @throws Base64DecoderException
*/
- public static byte[] decode(byte[] source, int off, int len)
- throws Base64DecoderException {
- return decode(source, off, len, DECODABET);
- }
+ public static byte[] decode(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, DECODABET);
+ }
- /**
+ /**
* Decodes web safe Base64 content in byte array format and returns
* the decoded byte array.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
@@ -473,12 +484,12 @@ public class Base64 {
* @param len The length of characters to decode
* @return decoded data
*/
- public static byte[] decodeWebSafe(byte[] source, int off, int len)
- throws Base64DecoderException {
- return decode(source, off, len, WEBSAFE_DECODABET);
- }
+ public static byte[] decodeWebSafe(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, WEBSAFE_DECODABET);
+ }
- /**
+ /**
* Decodes Base64 content using the supplied decodabet and returns
* the decoded byte array.
*
@@ -488,69 +499,72 @@ public class Base64 {
* @param decodabet the decodabet for decoding Base64 content
* @return decoded data
*/
- public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
- throws Base64DecoderException {
- int len34 = len * 3 / 4;
- byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
- int outBuffPosn = 0;
-
- byte[] b4 = new byte[4];
- int b4Posn = 0;
- int i = 0;
- byte sbiCrop = 0;
- byte sbiDecode = 0;
- for (i = 0; i < len; i++) {
- sbiCrop = (byte)(source[i + off] & 0x7f); // Only the low seven bits
- sbiDecode = decodabet[sbiCrop];
-
- if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
- if (sbiDecode >= EQUALS_SIGN_ENC) {
- // An equals sign (for padding) must not occur at position 0 or 1
- // and must be the last byte[s] in the encoded value
- if (sbiCrop == EQUALS_SIGN) {
- int bytesLeft = len - i;
- byte lastByte = (byte)(source[len - 1 + off] & 0x7f);
- if (b4Posn == 0 || b4Posn == 1) {
- throw new Base64DecoderException(
- "invalid padding byte '=' at byte offset " + i);
- } else if ((b4Posn == 3 && bytesLeft > 2) || (b4Posn == 4 && bytesLeft > 1)) {
- throw new Base64DecoderException(
- "padding byte '=' falsely signals end of encoded value "
- + "at offset " + i);
- } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
- throw new Base64DecoderException(
- "encoded value has invalid trailing byte");
- }
- break;
- }
-
- b4[b4Posn++] = sbiCrop;
- if (b4Posn == 4) {
- outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
- b4Posn = 0;
- }
- }
- } else {
- throw new Base64DecoderException("Bad Base64 input character at " + i + ": " + source[i + off] + "(decimal)");
- }
- }
-
- // Because web safe encoding allows non padding base64 encodes, we
- // need to pad the rest of the b4 buffer with equal signs when
- // b4Posn != 0. There can be at most 2 equal signs at the end of
- // four characters, so the b4 buffer must have two or three
- // characters. This also catches the case where the input is
- // padded with EQUALS_SIGN
- if (b4Posn != 0) {
- if (b4Posn == 1) {
- throw new Base64DecoderException("single trailing character at offset " + (len - 1));
- }
- b4[b4Posn++] = EQUALS_SIGN;
- outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
- }
-
- byte[] out = new byte[outBuffPosn];
- System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
- return out;
- }
+ public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
+ throws Base64DecoderException {
+ int len34 = len * 3 / 4;
+ byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
+ int outBuffPosn = 0;
+
+ byte[] b4 = new byte[4];
+ int b4Posn = 0;
+ int i = 0;
+ byte sbiCrop = 0;
+ byte sbiDecode = 0;
+ for (i = 0; i < len; i++) {
+ sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
+ sbiDecode = decodabet[sbiCrop];
+
+ if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
+ if (sbiDecode >= EQUALS_SIGN_ENC) {
+ // An equals sign (for padding) must not occur at position 0 or 1
+ // and must be the last byte[s] in the encoded value
+ if (sbiCrop == EQUALS_SIGN) {
+ int bytesLeft = len - i;
+ byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
+ if (b4Posn == 0 || b4Posn == 1) {
+ throw new Base64DecoderException(
+ "invalid padding byte '=' at byte offset " + i);
+ } else if ((b4Posn == 3 && bytesLeft > 2)
+ || (b4Posn == 4 && bytesLeft > 1)) {
+ throw new Base64DecoderException(
+ "padding byte '=' falsely signals end of encoded value "
+ + "at offset " + i);
+ } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
+ throw new Base64DecoderException(
+ "encoded value has invalid trailing byte");
+ }
+ break;
+ }
+
+ b4[b4Posn++] = sbiCrop;
+ if (b4Posn == 4) {
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ b4Posn = 0;
+ }
+ }
+ } else {
+ throw new Base64DecoderException("Bad Base64 input character at " + i
+ + ": " + source[i + off] + "(decimal)");
+ }
+ }
+
+ // Because web safe encoding allows non padding base64 encodes, we
+ // need to pad the rest of the b4 buffer with equal signs when
+ // b4Posn != 0. There can be at most 2 equal signs at the end of
+ // four characters, so the b4 buffer must have two or three
+ // characters. This also catches the case where the input is
+ // padded with EQUALS_SIGN
+ if (b4Posn != 0) {
+ if (b4Posn == 1) {
+ throw new Base64DecoderException("single trailing character at offset "
+ + (len - 1));
+ }
+ b4[b4Posn++] = EQUALS_SIGN;
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ }
+
+ byte[] out = new byte[outBuffPosn];
+ System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+ return out;
+ }
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java b/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java
index 50724a9b05..1aef1b54b8 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/util/Base64DecoderException.java
@@ -20,13 +20,13 @@ package com.google.android.vending.licensing.util;
* @author nelson
*/
public class Base64DecoderException extends Exception {
- public Base64DecoderException() {
- super();
- }
+ public Base64DecoderException() {
+ super();
+ }
- public Base64DecoderException(String s) {
- super(s);
- }
+ public Base64DecoderException(String s) {
+ super(s);
+ }
- private static final long serialVersionUID = 1L;
+ private static final long serialVersionUID = 1L;
}
diff --git a/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java b/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java
index 4f908b472c..5155bf5ac3 100644
--- a/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java
+++ b/platform/android/java/src/com/google/android/vending/licensing/util/URIQueryDecoder.java
@@ -25,36 +25,36 @@ import java.util.Map;
import java.util.Scanner;
public class URIQueryDecoder {
- private static final String TAG = "URIQueryDecoder";
+ private static final String TAG = "URIQueryDecoder";
- /**
+ /**
* Decodes the query portion of the passed-in URI.
*
* @param encodedURI the URI containing the query to decode
* @param results a map containing all query parameters. Query parameters that do not have a
* value will map to a null string
*/
- static public void DecodeQuery(URI encodedURI, Map<String, String> results) {
- Scanner scanner = new Scanner(encodedURI.getRawQuery());
- scanner.useDelimiter("&");
- try {
- while (scanner.hasNext()) {
- String param = scanner.next();
- String[] valuePair = param.split("=");
- String name, value;
- if (valuePair.length == 1) {
- value = null;
- } else if (valuePair.length == 2) {
- value = URLDecoder.decode(valuePair[1], "UTF-8");
- } else {
- throw new IllegalArgumentException("query parameter invalid");
- }
- name = URLDecoder.decode(valuePair[0], "UTF-8");
- results.put(name, value);
- }
- } catch (UnsupportedEncodingException e) {
- // This should never happen.
- Log.e(TAG, "UTF-8 Not Recognized as a charset. Device configuration Error.");
- }
- }
+ static public void DecodeQuery(URI encodedURI, Map<String, String> results) {
+ Scanner scanner = new Scanner(encodedURI.getRawQuery());
+ scanner.useDelimiter("&");
+ try {
+ while (scanner.hasNext()) {
+ String param = scanner.next();
+ String[] valuePair = param.split("=");
+ String name, value;
+ if (valuePair.length == 1) {
+ value = null;
+ } else if (valuePair.length == 2) {
+ value = URLDecoder.decode(valuePair[1], "UTF-8");
+ } else {
+ throw new IllegalArgumentException("query parameter invalid");
+ }
+ name = URLDecoder.decode(valuePair[0], "UTF-8");
+ results.put(name, value);
+ }
+ } catch (UnsupportedEncodingException e) {
+ // This should never happen.
+ Log.e(TAG, "UTF-8 Not Recognized as a charset. Device configuration Error.");
+ }
+ }
}