Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Adjust/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ext {
coreMinSdkVersion = 21
coreCompileSdkVersion = 36
coreTargetSdkVersion = 36
coreVersionName = '5.4.5'
coreVersionName = '5.4.6'
defaultVersionCode = 1
webbridgeMinSdkVersion = 21
samsungReferrerMinSdkVersion = 21
Expand Down
1 change: 1 addition & 0 deletions Adjust/examples/example-app-kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ dependencies {
implementation 'com.google.android.gms:play-services-appset:16.1.0'

implementation project(':sdk-core')

implementation 'com.adjust.signature:adjust-android-signature:3.61.0'
}
6 changes: 1 addition & 5 deletions Adjust/plugins/sdk-plugin-google-lvl/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@
# Keep all classes in the LVL licensing AIDL package
-keep class com.android.vending.licensing.** { *; }

# Keep interface method signatures
-keep interface com.android.vending.licensing.ILicensingService
-keep interface com.android.vending.licensing.ILicenseResultListener

# Prevent obfuscation of the stub classes used for IPC
-keepclassmembers class * implements android.os.IInterface {
<methods>;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,35 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

import com.adjust.sdk.ILogger;
import com.android.vending.licensing.ILicensingService;

public class LicenseChecker {
private static final String GOOGLE_PLAY_PACKAGE = "com.android.vending";
private static final String LICENSING_SERVICE_DESCRIPTOR = "com.android.vending.licensing.ILicensingService";
private static final String RESULT_LISTENER_DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener";
private static final int TRANSACTION_CHECK_LICENSE = IBinder.FIRST_CALL_TRANSACTION;

// Error codes for better diagnostics
private static final int ERROR_GENERIC = -1;
private static final int ERROR_BIND_FAILED = -5;
private static final int ERROR_NO_BINDER = -3;
private static final int ERROR_TRANSACT_FAILED = -2;
private static final int ERROR_REMOTE_EXCEPTION = -4;
private static final int ERROR_BINDER_DIED = -6;

private final Context mContext;
private final LicenseRawCallback mCallback;
private final ILogger logger;
private final long timestamp;
private final ResultListenerBinder resultListenerBinder;

private ILicensingService mService;
private IBinder mServiceBinder;
private boolean mBound;
private int retryCount = 0;
private static final int MAX_RETRIES = 3;
Expand All @@ -28,6 +42,7 @@ public LicenseChecker(Context context, LicenseRawCallback callback, ILogger logg
this.mCallback = callback;
this.logger = logger;
this.timestamp = timestamp;
this.resultListenerBinder = new ResultListenerBinder();
}

public synchronized void checkAccess() {
Expand All @@ -40,6 +55,11 @@ public synchronized void checkAccess() {
serviceIntent.setPackage(GOOGLE_PLAY_PACKAGE);
boolean isBind = mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
logger.debug("LVL bindService result: " + isBind);

if (!isBind) {
logger.error("LVL failed to bind licensing service");
mCallback.onError(ERROR_BIND_FAILED);
}
}

public void onDestroy() {
Expand All @@ -48,53 +68,128 @@ public void onDestroy() {
mContext.unbindService(mServiceConnection);
mBound = false;
}
// Ensure binder reference is cleared even if unbind fails
mServiceBinder = null;
}

private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
logger.debug("LVL service connected");
mService = ILicensingService.Stub.asInterface(service);
mServiceBinder = service;
mBound = true;
retryCount = 0;

// Detect binder death proactively
try {
mServiceBinder.linkToDeath(() -> {
logger.error("LVL binder died unexpectedly");
mCallback.onError(ERROR_BINDER_DIED);
onDestroy();
}, 0);
} catch (RemoteException e) {
logger.error("LVL failed to link binder death recipient", e);
}

executeLicenseCheck();
}

@Override
public void onServiceDisconnected(ComponentName name) {
logger.debug("LVL service disconnected");
mService = null;
mServiceBinder = null;
mBound = false;
}
};

private void executeLicenseCheck() {
try {
if (mServiceBinder == null) {
logger.error("LVL binder unavailable for license check");
mCallback.onError(ERROR_NO_BINDER);
return;
}

String packageName = mContext.getPackageName();
long nonce = generateNonce(timestamp);
logger.debug("LVL generated nonce: " + nonce);

mService.checkLicense(nonce, packageName, new ResultListener());
Parcel data = Parcel.obtain();
try {
data.writeInterfaceToken(LICENSING_SERVICE_DESCRIPTOR);
data.writeLong(nonce);
data.writeString(packageName);
data.writeStrongBinder(resultListenerBinder);
boolean transacted = mServiceBinder.transact(
TRANSACTION_CHECK_LICENSE,
data,
null,
IBinder.FLAG_ONEWAY
);
logger.debug("LVL binder transact sent (code " + TRANSACTION_CHECK_LICENSE + "): " + transacted);
if (!transacted) {
logger.error("LVL binder transact failed to enqueue");
mCallback.onError(ERROR_TRANSACT_FAILED);
}
} finally {
data.recycle();
}
} catch (RemoteException e) {
logger.error("LVL remote exception during license check: ", e);
mCallback.onError(ERROR_REMOTE_EXCEPTION);
} catch (Exception e) {
logger.error("LVL license check failed: ", e);
mCallback.onError(-1);
mCallback.onError(ERROR_GENERIC);
}
}

private class ResultListener extends com.android.vending.licensing.ILicenseResultListener.Stub {
private class ResultListenerBinder extends Binder implements IInterface {
ResultListenerBinder() {
attachInterface(this, RESULT_LISTENER_DESCRIPTOR);
}

@Override
public IBinder asBinder() {
return this;
}

@Override
public void verifyLicense(int responseCode, String signedData, String signature) throws RemoteException {
logger.debug("LVL Received license response: " + responseCode);

LicenseResponseHandler handler = new LicenseResponseHandler(mCallback, logger, MAX_RETRIES);
boolean shouldRetry = handler.handleResponse(responseCode, signedData, signature, retryCount);
if (shouldRetry) {
retryCount++;
logger.debug("LVL retrying license check... Attempt: " + retryCount);
executeLicenseCheck();
} else {
onDestroy();
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (code == IBinder.INTERFACE_TRANSACTION) {
if (reply != null) {
reply.writeString(RESULT_LISTENER_DESCRIPTOR);
}
return true;
}

if (code == IBinder.FIRST_CALL_TRANSACTION) {
try {
data.enforceInterface(RESULT_LISTENER_DESCRIPTOR);
int responseCode = data.readInt();
String signedData = data.readString();
String signature = data.readString();

logger.debug("LVL received license response: " + responseCode);

LicenseResponseHandler handler = new LicenseResponseHandler(mCallback, logger, MAX_RETRIES);
boolean shouldRetry = handler.handleResponse(responseCode, signedData, signature, retryCount);
if (shouldRetry) {
retryCount++;
logger.debug("LVL retrying license check... Attempt: " + retryCount);
executeLicenseCheck();
} else {
onDestroy();
}
return true;
} catch (Exception ex) {
logger.error("LVL failed to process license response: ", ex);
mCallback.onError(ERROR_GENERIC);
onDestroy();
return true;
}
}

return super.onTransact(code, data, reply, flags);
}
}

Expand All @@ -111,5 +206,4 @@ public static long generateNonce(long timestamp) {
// Shift timestamp to occupy bits 8–63, reserve LSB for flags/version
return (reducedTimestamp << 8) | 0x01;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.content.ServiceConnection;
import android.os.Parcel;
import android.os.RemoteException;

import com.adjust.sdk.ILogger;
import com.adjust.sdk.google.lvl.LicenseChecker;
import com.adjust.sdk.google.lvl.LicenseRawCallback;
import com.android.vending.licensing.ILicenseResultListener;
import com.android.vending.licensing.ILicensingService;

import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -43,10 +41,38 @@ public void setUp() {
public void testSuccessfulLicenseCheck_shouldCallCallback() {
doAnswer(invocation -> {
ServiceConnection conn = invocation.getArgument(1);
IBinder binder = new ILicensingService.Stub() {
IBinder binder = new Binder() {
private static final String SERVICE_DESCRIPTOR = "com.android.vending.licensing.ILicensingService";
private static final String LISTENER_DESCRIPTOR = "com.android.vending.licensing.ILicenseResultListener";

@Override
public void checkLicense(long nonce, String packageName, ILicenseResultListener listener) throws RemoteException {
listener.verifyLicense(0, "signedData", "signature");
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (code == IBinder.INTERFACE_TRANSACTION) {
if (reply != null) {
reply.writeString(SERVICE_DESCRIPTOR);
}
return true;
}

if (code == IBinder.FIRST_CALL_TRANSACTION) {
data.enforceInterface(SERVICE_DESCRIPTOR);
long nonce = data.readLong();
String packageName = data.readString();
IBinder listenerBinder = data.readStrongBinder();

Parcel response = Parcel.obtain();
try {
response.writeInterfaceToken(LISTENER_DESCRIPTOR);
response.writeInt(0);
response.writeString("signedData");
response.writeString("signature");
listenerBinder.transact(IBinder.FIRST_CALL_TRANSACTION, response, null, IBinder.FLAG_ONEWAY);
} finally {
response.recycle();
}
return true;
}
return super.onTransact(code, data, reply, flags);
}
};
conn.onServiceConnected(new ComponentName("com.android.vending", "LicensingService"), binder);
Expand Down
2 changes: 1 addition & 1 deletion Adjust/plugins/sdk-plugin-huawei-referrer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
// Add SDK via module.
compileOnly project(':sdk-core')
// Add SDK via Maven.
// implementation 'com.adjust.sdk:adjust-android:5.4.5'
// implementation 'com.adjust.sdk:adjust-android:5.4.6'
}

// read local properties
Expand Down
2 changes: 1 addition & 1 deletion Adjust/plugins/sdk-plugin-imei/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies {
// Add SDK via module.
compileOnly project(':sdk-core')
// Add SDK via Maven.
// implementation 'com.adjust.sdk:adjust-android:5.4.5'
// implementation 'com.adjust.sdk:adjust-android:5.4.6'
}

// read local properties
Expand Down
2 changes: 1 addition & 1 deletion Adjust/plugins/sdk-plugin-meta-referrer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
// Add SDK via module.
compileOnly project(':sdk-core')
// Add SDK via Maven.
// implementation 'com.adjust.sdk:adjust-android:5.4.5'
// implementation 'com.adjust.sdk:adjust-android:5.4.6'
}

// read local properties
Expand Down
2 changes: 1 addition & 1 deletion Adjust/plugins/sdk-plugin-oaid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies {
// Add SDK via module.
compileOnly project(':sdk-core')
// Add SDK via Maven.
// implementation 'com.adjust.sdk:adjust-android:5.4.5'
// implementation 'com.adjust.sdk:adjust-android:5.4.6'
implementation 'com.huawei.hms:ads-identifier:3.4.56.300'
}

Expand Down
2 changes: 1 addition & 1 deletion Adjust/plugins/sdk-plugin-samsung-clouddev/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
// Add SDK via module.
compileOnly project(':sdk-core')
// Add SDK via Maven.
// implementation 'com.adjust.sdk:adjust-android:5.4.5'
// implementation 'com.adjust.sdk:adjust-android:5.4.6'

// Add Samsung clouddev lib.
compileOnly fileTree(include: ['*.aar'], dir: 'libs')
Expand Down
2 changes: 1 addition & 1 deletion Adjust/plugins/sdk-plugin-samsung-referrer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
// Add SDK via module.
compileOnly project(':sdk-core')
// Add SDK via Maven.
// implementation 'com.adjust.sdk:adjust-android:5.4.5'
// implementation 'com.adjust.sdk:adjust-android:5.4.6'

// Add Samsung referrer lib via Maven.
implementation 'store.galaxy.samsung.installreferrer:samsung_galaxystore_install_referrer:3.0.1'
Expand Down
Loading