Loading vending-app/build.gradle +22 −2 Original line number Diff line number Diff line Loading @@ -4,6 +4,8 @@ */ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'com.squareup.wire' android { namespace "com.android.vending" Loading @@ -28,18 +30,36 @@ android { } } sourceSets { main { java { srcDirs += "build/generated/source/proto/main/java" } } } buildFeatures { aidl = true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } } dependencies { implementation project(':fake-signature') implementation "com.squareup.wire:wire-runtime:$wireVersion" implementation "com.android.volley:volley:$volleyVersion" } wire { kotlin { javaInterop = true } } if (file('user.gradle').exists()) { Loading vending-app/src/main/AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <permission android:name="com.android.vending.CHECK_LICENSE" android:protectionLevel="normal" /> Loading vending-app/src/main/java/com/android/vending/Util.java 0 → 100644 +28 −0 Original line number Diff line number Diff line package com.android.vending; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPOutputStream; public class Util { private static final String TAG = "FakeStoreUtil"; /** * From <a href="https://stackoverflow.com/a/46688434/">StackOverflow</a>, CC BY-SA 4.0 by Sergey Frolov, adapted. */ public static byte[] encodeGzip(final byte[] input) { try (final ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); final GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput)) { gzipOutput.write(input); gzipOutput.finish(); return byteOutput.toByteArray(); } catch (IOException e) { Log.e(TAG, "Failed to encode bytes as GZIP"); return new byte[0]; } } } vending-app/src/main/java/com/android/vending/licensing/LicenseRequest.java 0 → 100644 +215 −0 Original line number Diff line number Diff line package com.android.vending.licensing; import static com.android.volley.Request.Method.GET; import android.util.Base64; import android.util.Log; import com.android.vending.AndroidVersionMeta; import com.android.vending.DeviceMeta; import com.android.vending.EncodedTriple; import com.android.vending.EncodedTripleWrapper; import com.android.vending.IntWrapper; import com.android.vending.LicenseRequestHeader; import com.android.vending.LicenseResult; import com.android.vending.Locality; import com.android.vending.LocalityWrapper; import com.android.vending.StringWrapper; import com.android.vending.Timestamp; import com.android.vending.TimestampContainer; import com.android.vending.TimestampContainer1; import com.android.vending.TimestampContainer1Wrapper; import com.android.vending.TimestampContainer2; import com.android.vending.TimestampStringWrapper; import com.android.vending.TimestampWrapper; import com.android.vending.UnknownByte12; import com.android.vending.UserAgent; import com.android.vending.Util; import com.android.vending.Uuid; import com.android.vending.V1Container; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import java.io.IOException; import java.util.Map; import java.util.UUID; import okio.ByteString; public abstract class LicenseRequest<T> extends Request<T> { private final String xPsRh; private static final String TAG = "FakeLicenseRequest"; private static final int BASE64_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING; private static final long ANDROID_ID = 1; private final Response.Listener<T> successListener; protected LicenseRequest(String url, Response.Listener<T> successListener, Response.ErrorListener errorListener) { super(GET, url, errorListener); this.successListener = successListener; long millis = System.currentTimeMillis(); TimestampContainer.Builder timestamp = new TimestampContainer.Builder() .container2(new TimestampContainer2.Builder() .wrapper(new TimestampWrapper.Builder().timestamp(makeTimestamp(millis)).build()) .timestamp(makeTimestamp(millis)) .build()); millis = System.currentTimeMillis(); timestamp .container1Wrapper(new TimestampContainer1Wrapper.Builder() .androidId(String.valueOf(ANDROID_ID)) .container(new TimestampContainer1.Builder() .timestamp(millis + "000") .wrapper(makeTimestamp(millis)) .build()) .build() ); String encodedTimestamps = new String( Base64.encode(Util.encodeGzip(timestamp.build().encode()), BASE64_FLAGS) ); Locality locality = new Locality.Builder() .unknown1(1) .unknown2(2) .countryCode("") .region(new TimestampStringWrapper.Builder() .string("").timestamp(makeTimestamp(System.currentTimeMillis())).build()) .country(new TimestampStringWrapper.Builder() .string("").timestamp(makeTimestamp(System.currentTimeMillis())).build()) .unknown3(0) .build(); String encodedLocality = new String( Base64.encode(locality.encode(), BASE64_FLAGS) ); byte[] header = new LicenseRequestHeader.Builder() .encodedTimestamps(new StringWrapper.Builder().string(encodedTimestamps).build()) .triple( new EncodedTripleWrapper.Builder().triple( new EncodedTriple.Builder() .encoded1("") .encoded2("") .empty("") .build() ).build() ) .locality(new LocalityWrapper.Builder().encodedLocalityProto(encodedLocality).build()) .unknown(new IntWrapper.Builder().integer(5).build()) .empty("") .deviceMeta(new DeviceMeta.Builder() .android( new AndroidVersionMeta.Builder() .androidSdk(0) .buildNumber("") .androidVersion("") .unknown(0) .build() ) .unknown1(new UnknownByte12.Builder().bytes(new ByteString(new byte[]{} )).build()) .unknown2(1) .build() ) .userAgent(new UserAgent.Builder() .deviceProductName("") .deviceSoc("") .deviceModelName("") .finskyVersion("") .deviceName("") .androidId(ANDROID_ID) // must not be 0 .deviceSignature("") .build() ) .uuid(new Uuid.Builder() .uuid(UUID.randomUUID().toString()) .unknown(2) .build() ) .build().encode(); this.xPsRh = new String(Base64.encode(Util.encodeGzip(header), BASE64_FLAGS)); //Log.d(TAG, "Product " + Build.PRODUCT + ", Board " + Build.BOARD + " Model " + Build.MODEL + " Device " + Build.DEVICE); Log.v(TAG, "X-PS-RH: " + xPsRh); } @Override public Map<String, String> getHeaders() { return Map.of( "X-PS-RH", xPsRh, "Authorization", "Bearer ya29.[…]]", "Connection", "Keep-Alive" ); } @Override protected void deliverResponse(T response) { successListener.onResponse(response); } private static Timestamp makeTimestamp(long millis) { return new Timestamp.Builder() .seconds((int) (millis / 1000)) .nanos(Math.floorMod(millis, 1000) * 1000000) .build(); } public static class V1 extends LicenseRequest<V1Container> { public V1(String packageName, int versionCode, long nonce, Response.Listener<V1Container> successListener, Response.ErrorListener errorListener) { super("https://play-fe.googleapis.com/fdfe/apps/checkLicense?pkgn=" + packageName + "&vc=" + versionCode + "&nnc=" + nonce, successListener, errorListener ); } @Override protected Response<V1Container> parseNetworkResponse(NetworkResponse response) { if (response != null && response.data != null) { try { LicenseResult result = LicenseResult.ADAPTER.decode(response.data); return Response.success(result.information.v1, null); } catch (IOException e) { return Response.error(new VolleyError(e)); } catch (NullPointerException e) { // A field does not exist → user has no license return Response.success(null, null); } } else { return Response.error(new VolleyError("No response was returned")); } } } public static class V2 extends LicenseRequest<String> { public V2(String packageName, int versionCode, Response.Listener<String> successListener, Response.ErrorListener errorListener) { super( "https://play-fe.googleapis.com/fdfe/apps/checkLicenseServerFallback?pkgn=" + packageName + "&vc=" + versionCode, successListener, errorListener ); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { if (response != null && response.data != null) { try { LicenseResult result = LicenseResult.ADAPTER.decode(response.data); return Response.success(result.information.v2.license.jwt, null); } catch (IOException e) { return Response.error(new VolleyError(e)); } catch (NullPointerException e) { // A field does not exist → user has no license return Response.success(null, null); } } else { return Response.error(new VolleyError("No response was returned")); } } } } vending-app/src/main/java/com/android/vending/licensing/LicensingService.java +149 −4 Original line number Diff line number Diff line Loading @@ -7,30 +7,175 @@ package com.android.vending.licensing; import android.app.Service; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; public class LicensingService extends Service { private static final String TAG = "FakeLicenseService"; private RequestQueue queue; private static final String KEY_V2_RESULT_JWT = "LICENSE_DATA"; private final ILicensingService.Stub mLicenseService = new ILicensingService.Stub() { /* Possible response codes for checkLicense v1, from * https://developer.android.com/google/play/licensing/licensing-reference#server-response-codes and * the LVL library. */ /** * The application is licensed to the user. The user has purchased the application, or is authorized to * download and install the alpha or beta version of the application. */ private static final int LICENSED = 0x0; /** * The application is licensed to the user, but there is an updated application version available that is * signed with a different key. */ private static final int NOT_LICENSED = 0x1; /** * The application is not licensed to the user. */ private static final int LICENSED_OLD_KEY = 0x2; /** * Server error — the application (package name) was not recognized by Google Play. */ private static final int ERROR_NOT_MARKET_MANAGED = 0x3; /** * Server error — the server could not load the application's key pair for licensing. */ private static final int ERROR_SERVER_FAILURE = 0x4; private static final int ERROR_OVER_QUOTA = 0x5; /** * Local error — the Google Play application was not able to reach the licensing server, possibly because * of network availability problems. */ private static final int ERROR_CONTACTING_SERVER = 0x101; /** * Local error — the application requested a license check for a package that is not installed on the device. */ private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; /** * Local error — the application requested a license check for a package whose UID (package, user ID pair) * does not match that of the requesting application. */ private static final int ERROR_NON_MATCHING_UID = 0x103; @Override public void checkLicense(long nonce, String packageName, ILicenseResultListener listener) throws RemoteException { Log.d(TAG, "checkLicense(" + nonce + ", " + packageName + ")"); // We don't return anything yet. Seems to work good for some checkers. Log.v(TAG, "checkLicense(" + nonce + ", " + packageName + ")"); try { PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0); int versionCode = packageInfo.versionCode; // Verify caller identity if (packageInfo.applicationInfo.uid != getCallingUid()) { Log.e(TAG, "an app illegally tried to request v1 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, null, null); } else { Request request = new LicenseRequest.V1(packageName, versionCode, nonce, data -> { try { if (data != null) { Log.v(TAG, "licenseV1 result was " + data.result + "with signed data " + data.signedData); if (data.result != null) { listener.verifyLicense(data.result, data.signedData, data.signature); } else { listener.verifyLicense(LICENSED, data.signedData, data.signature); } } else { Log.v(TAG, "licenseV1 result was that user has no license"); listener.verifyLicense(NOT_LICENSED, null, null); } } catch (RemoteException e) { Log.e(TAG, "After returning licenseV1 result, remote threw an Exception."); e.printStackTrace(); } }, error -> { Log.e(TAG, "licenseV1 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, null, null); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV1 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } }); request.setShouldCache(false); queue.add(request); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); listener.verifyLicense(ERROR_INVALID_PACKAGE_NAME, null, null); } } @Override public void checkLicenseV2(String packageName, ILicenseV2ResultListener listener, Bundle extraParams) throws RemoteException { Log.d(TAG, "checkLicenseV2(" + packageName + ", " + extraParams + ")"); // We don't return anything yet. Seems to work good for some checkers. Log.v(TAG, "checkLicenseV2(" + packageName + ", " + extraParams + ")"); try { PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0); int versionCode = packageInfo.versionCode; // Verify caller identity if (packageInfo.applicationInfo.uid != getCallingUid()) { Log.e(TAG, "an app illegally tried to request v2 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, new Bundle()); } else { Request request = new LicenseRequest.V2(packageName, versionCode, jwt -> { Log.v(TAG, "LicenseV2 returned JWT license value " + jwt); Bundle bundle = new Bundle(); bundle.putString(KEY_V2_RESULT_JWT, jwt); try { listener.verifyLicense(jwt == null? NOT_LICENSED : LICENSED, bundle); } catch (RemoteException e) { Log.e(TAG, "After returning licenseV2 result, remote threw an Exception."); e.printStackTrace(); } }, error -> { Log.e(TAG, "licenseV2 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, new Bundle()); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV2 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } }); request.setShouldCache(false); queue.add(request); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); listener.verifyLicense(ERROR_INVALID_PACKAGE_NAME, new Bundle()); } } }; public IBinder onBind(Intent intent) { queue = Volley.newRequestQueue(this); return mLicenseService; } } Loading
vending-app/build.gradle +22 −2 Original line number Diff line number Diff line Loading @@ -4,6 +4,8 @@ */ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'com.squareup.wire' android { namespace "com.android.vending" Loading @@ -28,18 +30,36 @@ android { } } sourceSets { main { java { srcDirs += "build/generated/source/proto/main/java" } } } buildFeatures { aidl = true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } } dependencies { implementation project(':fake-signature') implementation "com.squareup.wire:wire-runtime:$wireVersion" implementation "com.android.volley:volley:$volleyVersion" } wire { kotlin { javaInterop = true } } if (file('user.gradle').exists()) { Loading
vending-app/src/main/AndroidManifest.xml +2 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <permission android:name="com.android.vending.CHECK_LICENSE" android:protectionLevel="normal" /> Loading
vending-app/src/main/java/com/android/vending/Util.java 0 → 100644 +28 −0 Original line number Diff line number Diff line package com.android.vending; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPOutputStream; public class Util { private static final String TAG = "FakeStoreUtil"; /** * From <a href="https://stackoverflow.com/a/46688434/">StackOverflow</a>, CC BY-SA 4.0 by Sergey Frolov, adapted. */ public static byte[] encodeGzip(final byte[] input) { try (final ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); final GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput)) { gzipOutput.write(input); gzipOutput.finish(); return byteOutput.toByteArray(); } catch (IOException e) { Log.e(TAG, "Failed to encode bytes as GZIP"); return new byte[0]; } } }
vending-app/src/main/java/com/android/vending/licensing/LicenseRequest.java 0 → 100644 +215 −0 Original line number Diff line number Diff line package com.android.vending.licensing; import static com.android.volley.Request.Method.GET; import android.util.Base64; import android.util.Log; import com.android.vending.AndroidVersionMeta; import com.android.vending.DeviceMeta; import com.android.vending.EncodedTriple; import com.android.vending.EncodedTripleWrapper; import com.android.vending.IntWrapper; import com.android.vending.LicenseRequestHeader; import com.android.vending.LicenseResult; import com.android.vending.Locality; import com.android.vending.LocalityWrapper; import com.android.vending.StringWrapper; import com.android.vending.Timestamp; import com.android.vending.TimestampContainer; import com.android.vending.TimestampContainer1; import com.android.vending.TimestampContainer1Wrapper; import com.android.vending.TimestampContainer2; import com.android.vending.TimestampStringWrapper; import com.android.vending.TimestampWrapper; import com.android.vending.UnknownByte12; import com.android.vending.UserAgent; import com.android.vending.Util; import com.android.vending.Uuid; import com.android.vending.V1Container; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import java.io.IOException; import java.util.Map; import java.util.UUID; import okio.ByteString; public abstract class LicenseRequest<T> extends Request<T> { private final String xPsRh; private static final String TAG = "FakeLicenseRequest"; private static final int BASE64_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING; private static final long ANDROID_ID = 1; private final Response.Listener<T> successListener; protected LicenseRequest(String url, Response.Listener<T> successListener, Response.ErrorListener errorListener) { super(GET, url, errorListener); this.successListener = successListener; long millis = System.currentTimeMillis(); TimestampContainer.Builder timestamp = new TimestampContainer.Builder() .container2(new TimestampContainer2.Builder() .wrapper(new TimestampWrapper.Builder().timestamp(makeTimestamp(millis)).build()) .timestamp(makeTimestamp(millis)) .build()); millis = System.currentTimeMillis(); timestamp .container1Wrapper(new TimestampContainer1Wrapper.Builder() .androidId(String.valueOf(ANDROID_ID)) .container(new TimestampContainer1.Builder() .timestamp(millis + "000") .wrapper(makeTimestamp(millis)) .build()) .build() ); String encodedTimestamps = new String( Base64.encode(Util.encodeGzip(timestamp.build().encode()), BASE64_FLAGS) ); Locality locality = new Locality.Builder() .unknown1(1) .unknown2(2) .countryCode("") .region(new TimestampStringWrapper.Builder() .string("").timestamp(makeTimestamp(System.currentTimeMillis())).build()) .country(new TimestampStringWrapper.Builder() .string("").timestamp(makeTimestamp(System.currentTimeMillis())).build()) .unknown3(0) .build(); String encodedLocality = new String( Base64.encode(locality.encode(), BASE64_FLAGS) ); byte[] header = new LicenseRequestHeader.Builder() .encodedTimestamps(new StringWrapper.Builder().string(encodedTimestamps).build()) .triple( new EncodedTripleWrapper.Builder().triple( new EncodedTriple.Builder() .encoded1("") .encoded2("") .empty("") .build() ).build() ) .locality(new LocalityWrapper.Builder().encodedLocalityProto(encodedLocality).build()) .unknown(new IntWrapper.Builder().integer(5).build()) .empty("") .deviceMeta(new DeviceMeta.Builder() .android( new AndroidVersionMeta.Builder() .androidSdk(0) .buildNumber("") .androidVersion("") .unknown(0) .build() ) .unknown1(new UnknownByte12.Builder().bytes(new ByteString(new byte[]{} )).build()) .unknown2(1) .build() ) .userAgent(new UserAgent.Builder() .deviceProductName("") .deviceSoc("") .deviceModelName("") .finskyVersion("") .deviceName("") .androidId(ANDROID_ID) // must not be 0 .deviceSignature("") .build() ) .uuid(new Uuid.Builder() .uuid(UUID.randomUUID().toString()) .unknown(2) .build() ) .build().encode(); this.xPsRh = new String(Base64.encode(Util.encodeGzip(header), BASE64_FLAGS)); //Log.d(TAG, "Product " + Build.PRODUCT + ", Board " + Build.BOARD + " Model " + Build.MODEL + " Device " + Build.DEVICE); Log.v(TAG, "X-PS-RH: " + xPsRh); } @Override public Map<String, String> getHeaders() { return Map.of( "X-PS-RH", xPsRh, "Authorization", "Bearer ya29.[…]]", "Connection", "Keep-Alive" ); } @Override protected void deliverResponse(T response) { successListener.onResponse(response); } private static Timestamp makeTimestamp(long millis) { return new Timestamp.Builder() .seconds((int) (millis / 1000)) .nanos(Math.floorMod(millis, 1000) * 1000000) .build(); } public static class V1 extends LicenseRequest<V1Container> { public V1(String packageName, int versionCode, long nonce, Response.Listener<V1Container> successListener, Response.ErrorListener errorListener) { super("https://play-fe.googleapis.com/fdfe/apps/checkLicense?pkgn=" + packageName + "&vc=" + versionCode + "&nnc=" + nonce, successListener, errorListener ); } @Override protected Response<V1Container> parseNetworkResponse(NetworkResponse response) { if (response != null && response.data != null) { try { LicenseResult result = LicenseResult.ADAPTER.decode(response.data); return Response.success(result.information.v1, null); } catch (IOException e) { return Response.error(new VolleyError(e)); } catch (NullPointerException e) { // A field does not exist → user has no license return Response.success(null, null); } } else { return Response.error(new VolleyError("No response was returned")); } } } public static class V2 extends LicenseRequest<String> { public V2(String packageName, int versionCode, Response.Listener<String> successListener, Response.ErrorListener errorListener) { super( "https://play-fe.googleapis.com/fdfe/apps/checkLicenseServerFallback?pkgn=" + packageName + "&vc=" + versionCode, successListener, errorListener ); } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { if (response != null && response.data != null) { try { LicenseResult result = LicenseResult.ADAPTER.decode(response.data); return Response.success(result.information.v2.license.jwt, null); } catch (IOException e) { return Response.error(new VolleyError(e)); } catch (NullPointerException e) { // A field does not exist → user has no license return Response.success(null, null); } } else { return Response.error(new VolleyError("No response was returned")); } } } }
vending-app/src/main/java/com/android/vending/licensing/LicensingService.java +149 −4 Original line number Diff line number Diff line Loading @@ -7,30 +7,175 @@ package com.android.vending.licensing; import android.app.Service; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; public class LicensingService extends Service { private static final String TAG = "FakeLicenseService"; private RequestQueue queue; private static final String KEY_V2_RESULT_JWT = "LICENSE_DATA"; private final ILicensingService.Stub mLicenseService = new ILicensingService.Stub() { /* Possible response codes for checkLicense v1, from * https://developer.android.com/google/play/licensing/licensing-reference#server-response-codes and * the LVL library. */ /** * The application is licensed to the user. The user has purchased the application, or is authorized to * download and install the alpha or beta version of the application. */ private static final int LICENSED = 0x0; /** * The application is licensed to the user, but there is an updated application version available that is * signed with a different key. */ private static final int NOT_LICENSED = 0x1; /** * The application is not licensed to the user. */ private static final int LICENSED_OLD_KEY = 0x2; /** * Server error — the application (package name) was not recognized by Google Play. */ private static final int ERROR_NOT_MARKET_MANAGED = 0x3; /** * Server error — the server could not load the application's key pair for licensing. */ private static final int ERROR_SERVER_FAILURE = 0x4; private static final int ERROR_OVER_QUOTA = 0x5; /** * Local error — the Google Play application was not able to reach the licensing server, possibly because * of network availability problems. */ private static final int ERROR_CONTACTING_SERVER = 0x101; /** * Local error — the application requested a license check for a package that is not installed on the device. */ private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; /** * Local error — the application requested a license check for a package whose UID (package, user ID pair) * does not match that of the requesting application. */ private static final int ERROR_NON_MATCHING_UID = 0x103; @Override public void checkLicense(long nonce, String packageName, ILicenseResultListener listener) throws RemoteException { Log.d(TAG, "checkLicense(" + nonce + ", " + packageName + ")"); // We don't return anything yet. Seems to work good for some checkers. Log.v(TAG, "checkLicense(" + nonce + ", " + packageName + ")"); try { PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0); int versionCode = packageInfo.versionCode; // Verify caller identity if (packageInfo.applicationInfo.uid != getCallingUid()) { Log.e(TAG, "an app illegally tried to request v1 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, null, null); } else { Request request = new LicenseRequest.V1(packageName, versionCode, nonce, data -> { try { if (data != null) { Log.v(TAG, "licenseV1 result was " + data.result + "with signed data " + data.signedData); if (data.result != null) { listener.verifyLicense(data.result, data.signedData, data.signature); } else { listener.verifyLicense(LICENSED, data.signedData, data.signature); } } else { Log.v(TAG, "licenseV1 result was that user has no license"); listener.verifyLicense(NOT_LICENSED, null, null); } } catch (RemoteException e) { Log.e(TAG, "After returning licenseV1 result, remote threw an Exception."); e.printStackTrace(); } }, error -> { Log.e(TAG, "licenseV1 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, null, null); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV1 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } }); request.setShouldCache(false); queue.add(request); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); listener.verifyLicense(ERROR_INVALID_PACKAGE_NAME, null, null); } } @Override public void checkLicenseV2(String packageName, ILicenseV2ResultListener listener, Bundle extraParams) throws RemoteException { Log.d(TAG, "checkLicenseV2(" + packageName + ", " + extraParams + ")"); // We don't return anything yet. Seems to work good for some checkers. Log.v(TAG, "checkLicenseV2(" + packageName + ", " + extraParams + ")"); try { PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0); int versionCode = packageInfo.versionCode; // Verify caller identity if (packageInfo.applicationInfo.uid != getCallingUid()) { Log.e(TAG, "an app illegally tried to request v2 licenses for another app (caller: " + getCallingUid() + ")"); listener.verifyLicense(ERROR_NON_MATCHING_UID, new Bundle()); } else { Request request = new LicenseRequest.V2(packageName, versionCode, jwt -> { Log.v(TAG, "LicenseV2 returned JWT license value " + jwt); Bundle bundle = new Bundle(); bundle.putString(KEY_V2_RESULT_JWT, jwt); try { listener.verifyLicense(jwt == null? NOT_LICENSED : LICENSED, bundle); } catch (RemoteException e) { Log.e(TAG, "After returning licenseV2 result, remote threw an Exception."); e.printStackTrace(); } }, error -> { Log.e(TAG, "licenseV2 request failed with " + error.toString()); try { listener.verifyLicense(ERROR_CONTACTING_SERVER, new Bundle()); } catch (RemoteException e) { Log.e(TAG, "After telling it that licenseV2 had an error when contacting server, remote threw an Exception."); e.printStackTrace(); Log.e(TAG, "Caused after network error:"); error.printStackTrace(); } }); request.setShouldCache(false); queue.add(request); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "an app tried to request v1 licenses for package " + packageName + ", which does not exist"); listener.verifyLicense(ERROR_INVALID_PACKAGE_NAME, new Bundle()); } } }; public IBinder onBind(Intent intent) { queue = Volley.newRequestQueue(this); return mLicenseService; } }