更新max

This commit is contained in:
2026-05-22 16:11:40 +08:00
parent dd55b961df
commit c8a5c4f2e5
69 changed files with 3719 additions and 3672 deletions
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin:applovin-sdk:13.0.0" />
<androidPackage spec="com.applovin:applovin-sdk:13.6.2" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinSDK" version="13.0.0" />
<iosPod name="AppLovinSDK" version="13.6.2" />
</iosPods>
</dependencies>
@@ -14,8 +14,8 @@ typedef void (*ALUnityBackgroundCallback)(const char* args);
- (void)initializeSdkWithConfiguration:(ALSdkInitializationConfiguration *)initConfig andCompletionHandler:(ALSdkInitializationCompletionHandler)completionHandler;
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)bannerPosition;
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset;
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)bannerPosition isAdaptive:(BOOL)isAdaptive;
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset isAdaptive:(BOOL)isAdaptive;
- (void)loadBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier;
- (void)setBannerBackgroundColorForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier hexColorCode:(nullable NSString *)hexColorCode;
- (void)setBannerPlacement:(nullable NSString *)placement forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier;
@@ -67,18 +67,9 @@ typedef void (*ALUnityBackgroundCallback)(const char* args);
- (void)setRewardedAdExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value;
- (void)setRewardedAdLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value;
- (void)loadRewardedInterstitialAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier;
- (BOOL)isRewardedInterstitialAdReadyWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier;
- (void)showRewardedInterstitialAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData;
- (void)setRewardedInterstitialAdExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value;
- (void)setRewardedInterstitialAdLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value;
// Event Tracking
- (void)trackEvent:(nullable NSString *)event parameters:(nullable NSString *)parameters;
// Ad Info
- (NSString *)adInfoForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier;
// Ad Value
- (NSString *)adValueForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier withKey:(nullable NSString *)key;
@@ -14,6 +14,8 @@
extern "C" {
#endif
extern bool max_unity_should_disable_all_logs(void); // Forward declaration
// UnityAppController.mm
UIViewController* UnityGetGLViewController(void);
UIWindow* UnityGetMainWindow(void);
@@ -40,7 +42,7 @@ extern "C" {
}
#endif
@interface MAUnityAdManager()<MAAdDelegate, MAAdViewAdDelegate, MARewardedAdDelegate, MAAdRevenueDelegate, MAAdReviewDelegate>
@interface MAUnityAdManager()<MAAdDelegate, MAAdViewAdDelegate, MARewardedAdDelegate, MAAdRevenueDelegate, MAAdReviewDelegate, MAAdExpirationDelegate>
// Parent Fields
@property (nonatomic, weak) ALSdk *sdk;
@@ -49,7 +51,6 @@ extern "C" {
@property (nonatomic, strong) NSMutableDictionary<NSString *, MAInterstitialAd *> *interstitials;
@property (nonatomic, strong) NSMutableDictionary<NSString *, MAAppOpenAd *> *appOpenAds;
@property (nonatomic, strong) NSMutableDictionary<NSString *, MARewardedAd *> *rewardedAds;
@property (nonatomic, strong) NSMutableDictionary<NSString *, MARewardedInterstitialAd *> *rewardedInterstitialAds;
// AdView Fields
@property (nonatomic, strong) NSMutableDictionary<NSString *, MAAdView *> *adViews;
@@ -65,6 +66,7 @@ extern "C" {
@property (nonatomic, strong) NSMutableArray<NSString *> *adUnitIdentifiersToShowAfterCreate;
@property (nonatomic, strong) NSMutableSet<NSString *> *disabledAdaptiveBannerAdUnitIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *disabledAutoRefreshAdViewAdUnitIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *ignoreSafeAreaLandscapeAdUnitIdentifiers;
@property (nonatomic, strong) UIView *safeAreaBackground;
@property (nonatomic, strong, nullable) UIColor *publisherBannerBackgroundColor;
@@ -110,7 +112,6 @@ static ALUnityBackgroundCallback backgroundCallback;
self.interstitials = [NSMutableDictionary dictionaryWithCapacity: 2];
self.appOpenAds = [NSMutableDictionary dictionaryWithCapacity: 2];
self.rewardedAds = [NSMutableDictionary dictionaryWithCapacity: 2];
self.rewardedInterstitialAds = [NSMutableDictionary dictionaryWithCapacity: 2];
self.adViews = [NSMutableDictionary dictionaryWithCapacity: 2];
self.adViewAdFormats = [NSMutableDictionary dictionaryWithCapacity: 2];
self.adViewPositions = [NSMutableDictionary dictionaryWithCapacity: 2];
@@ -124,6 +125,7 @@ static ALUnityBackgroundCallback backgroundCallback;
self.adUnitIdentifiersToShowAfterCreate = [NSMutableArray arrayWithCapacity: 2];
self.disabledAdaptiveBannerAdUnitIdentifiers = [NSMutableSet setWithCapacity: 2];
self.disabledAutoRefreshAdViewAdUnitIdentifiers = [NSMutableSet setWithCapacity: 2];
self.ignoreSafeAreaLandscapeAdUnitIdentifiers = [NSMutableSet setWithCapacity: 2];
self.adInfoDict = [NSMutableDictionary dictionary];
self.adInfoDictLock = [[NSObject alloc] init];
@@ -223,14 +225,14 @@ static ALUnityBackgroundCallback backgroundCallback;
#pragma mark - Banners
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)bannerPosition
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)bannerPosition isAdaptive:(BOOL)isAdaptive
{
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] atPosition: bannerPosition withOffset: CGPointZero];
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] atPosition: bannerPosition withOffset: CGPointZero isAdaptive: isAdaptive];
}
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset
- (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset isAdaptive:(BOOL)isAdaptive
{
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] atPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset)];
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] atPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset) isAdaptive: isAdaptive];
}
- (void)loadBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier
@@ -323,12 +325,12 @@ static ALUnityBackgroundCallback backgroundCallback;
- (void)createMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)mrecPosition
{
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec atPosition: mrecPosition withOffset: CGPointZero];
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec atPosition: mrecPosition withOffset: CGPointZero isAdaptive: NO];
}
- (void)createMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset
{
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec atPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset)];
[self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec atPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset) isAdaptive: NO];
}
- (void)loadMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier
@@ -516,44 +518,6 @@ static ALUnityBackgroundCallback backgroundCallback;
[rewardedAd setLocalExtraParameterForKey: key value: value];
}
#pragma mark - Rewarded Interstitials
- (void)loadRewardedInterstitialAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier
{
MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier];
[rewardedInterstitialAd loadAd];
}
- (BOOL)isRewardedInterstitialAdReadyWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier
{
MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier];
return [rewardedInterstitialAd isReady];
}
- (void)showRewardedInterstitialAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData
{
MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier];
[rewardedInterstitialAd showAdForPlacement: placement customData: customData];
}
- (void)setRewardedInterstitialAdExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value
{
MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier];
[rewardedInterstitialAd setExtraParameterForKey: key value: value];
}
- (void)setRewardedInterstitialAdLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value
{
if ( !key )
{
[self log: @"Failed to set local extra parameter: No key specified"];
return;
}
MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier];
[rewardedInterstitialAd setLocalExtraParameterForKey: key value: value];
}
#pragma mark - Event Tracking
- (void)trackEvent:(nullable NSString *)event parameters:(nullable NSString *)parameters
@@ -564,16 +528,6 @@ static ALUnityBackgroundCallback backgroundCallback;
#pragma mark - Ad Info
- (NSString *)adInfoForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier
{
if ( ![adUnitIdentifier al_isValidString] ) return @"";
MAAd *ad = [self adWithAdUnitIdentifier: adUnitIdentifier];
if ( !ad ) return @"";
return [MAUnityAdManager serializeParameters: [self adInfoForAd: ad]];
}
- (NSDictionary<NSString *, id> *)adInfoForAd:(MAAd *)ad
{
return @{@"adUnitId" : ad.adUnitIdentifier,
@@ -625,6 +579,7 @@ static ALUnityBackgroundCallback backgroundCallback;
networkInfoObject[@"adapterClassName"] = response.mediatedNetwork.adapterClassName;
networkInfoObject[@"adapterVersion"] = response.mediatedNetwork.adapterVersion;
networkInfoObject[@"sdkVersion"] = response.mediatedNetwork.sdkVersion;
networkInfoObject[@"initializationStatus"] = @(response.mediatedNetwork.initializationStatus);
networkResponseDict[@"mediatedNetwork"] = networkInfoObject;
}
@@ -703,10 +658,6 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnRewardedAdLoadedEvent";
}
else if ( MAAdFormat.rewardedInterstitial == adFormat )
{
name = @"OnRewardedInterstitialAdLoadedEvent";
}
else
{
[self logInvalidAdFormat: adFormat];
@@ -760,10 +711,6 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnRewardedAdLoadFailedEvent";
}
else if ( self.rewardedInterstitialAds[adUnitIdentifier] )
{
name = @"OnRewardedInterstitialAdLoadFailedEvent";
}
else
{
[self log: @"invalid adUnitId from %@", [NSThread callStackSymbols]];
@@ -811,10 +758,6 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnRewardedAdClickedEvent";
}
else if ( MAAdFormat.rewardedInterstitial == adFormat )
{
name = @"OnRewardedInterstitialAdClickedEvent";
}
else
{
[self logInvalidAdFormat: adFormat];
@@ -848,14 +791,10 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnAppOpenAdDisplayedEvent";
}
else if ( MAAdFormat.rewarded == adFormat )
else // rewarded
{
name = @"OnRewardedAdDisplayedEvent";
}
else // rewarded inters
{
name = @"OnRewardedInterstitialAdDisplayedEvent";
}
NSDictionary<NSString *, id> *args = [self defaultAdEventParametersForName: name withAd: ad];
[self forwardUnityEventWithArgs: args];
@@ -879,14 +818,10 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnAppOpenAdFailedToDisplayEvent";
}
else if ( MAAdFormat.rewarded == adFormat )
else // rewarded
{
name = @"OnRewardedAdFailedToDisplayEvent";
}
else // rewarded inters
{
name = @"OnRewardedInterstitialAdFailedToDisplayEvent";
}
NSMutableDictionary<NSString *, id> *args = [self defaultAdEventParametersForName: name withAd: ad];
args[@"errorCode"] = [@(error.code) stringValue];
@@ -930,14 +865,10 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnAppOpenAdHiddenEvent";
}
else if ( MAAdFormat.rewarded == adFormat )
else // rewarded
{
name = @"OnRewardedAdHiddenEvent";
}
else // rewarded inters
{
name = @"OnRewardedInterstitialAdHiddenEvent";
}
NSDictionary<NSString *, id> *args = [self defaultAdEventParametersForName: name withAd: ad];
[self forwardUnityEventWithArgs: args];
@@ -1020,7 +951,7 @@ static ALUnityBackgroundCallback backgroundCallback;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
MAAdFormat *adFormat = ad.format;
if ( adFormat != MAAdFormat.rewarded && adFormat != MAAdFormat.rewardedInterstitial )
if ( adFormat != MAAdFormat.rewarded )
{
[self logInvalidAdFormat: adFormat];
return;
@@ -1029,9 +960,7 @@ static ALUnityBackgroundCallback backgroundCallback;
NSString *rewardLabel = reward ? reward.label : @"";
NSInteger rewardAmountInt = reward ? reward.amount : 0;
NSString *rewardAmount = [@(rewardAmountInt) stringValue];
NSString *name = (adFormat == MAAdFormat.rewarded) ? @"OnRewardedAdReceivedRewardEvent" : @"OnRewardedInterstitialAdReceivedRewardEvent";
NSString *name = @"OnRewardedAdReceivedRewardEvent";
NSMutableDictionary<NSString *, id> *args = [self defaultAdEventParametersForName: name withAd: ad];
args[@"rewardLabel"] = rewardLabel;
@@ -1066,10 +995,6 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnRewardedAdRevenuePaidEvent";
}
else if ( MAAdFormat.rewardedInterstitial == adFormat )
{
name = @"OnRewardedInterstitialAdRevenuePaidEvent";
}
else
{
[self logInvalidAdFormat: adFormat];
@@ -1082,6 +1007,43 @@ static ALUnityBackgroundCallback backgroundCallback;
});
}
- (void)didReloadExpiredAd:(MAAd *)expiredAd withNewAd:(MAAd *)newAd;
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *name;
MAAdFormat *adFormat = newAd.format;
if ( MAAdFormat.interstitial == adFormat )
{
name = @"OnExpiredInterstitialAdReloadedEvent";
}
else if ( MAAdFormat.appOpen == adFormat )
{
name = @"OnExpiredAppOpenAdReloadedEvent";
}
else if ( MAAdFormat.rewarded == adFormat )
{
name = @"OnExpiredRewardedAdReloadedEvent ";
}
else
{
[self logInvalidAdFormat: adFormat];
return;
}
@synchronized ( self.adInfoDictLock )
{
self.adInfoDict[newAd.adUnitIdentifier] = newAd;
}
NSMutableDictionary<NSString *, NSObject *> *args = [NSMutableDictionary dictionary];
args[@"expiredAdInfo"] = [self adInfoForAd: expiredAd];
args[@"newAdInfo"] = [self adInfoForAd: newAd];
args[@"name"] = name;
[self forwardUnityEventWithArgs: args];
});
}
- (void)didGenerateCreativeIdentifier:(NSString *)creativeIdentifier forAd:(MAAd *)ad
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -1104,10 +1066,6 @@ static ALUnityBackgroundCallback backgroundCallback;
{
name = @"OnRewardedAdReviewCreativeIdGeneratedEvent";
}
else if ( MAAdFormat.rewardedInterstitial == adFormat )
{
name = @"OnRewardedInterstitialAdReviewCreativeIdGeneratedEvent";
}
else
{
[self logInvalidAdFormat: adFormat];
@@ -1133,7 +1091,7 @@ static ALUnityBackgroundCallback backgroundCallback;
#pragma mark - Internal Methods
- (void)createAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset
- (void)createAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset isAdaptive:(BOOL)isAdaptive
{
max_unity_dispatch_on_main_thread(^{
[self log: @"Creating %@ with ad unit identifier \"%@\" and position: \"%@\"", adFormat, adUnitIdentifier, adViewPosition];
@@ -1144,7 +1102,7 @@ static ALUnityBackgroundCallback backgroundCallback;
}
// Retrieve ad view from the map
MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: adViewPosition withOffset: offset];
MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: adViewPosition withOffset: offset isAdaptive: isAdaptive];
adView.hidden = YES;
self.safeAreaBackground.hidden = YES;
@@ -1154,16 +1112,6 @@ static ALUnityBackgroundCallback backgroundCallback;
NSDictionary<NSString *, NSString *> *extraParameters = self.adViewExtraParametersToSetAfterCreate[adUnitIdentifier];
// Enable adaptive banners by default for banners and leaders.
if ( [adFormat isBannerOrLeaderAd] )
{
// Check if there is already a pending setting for adaptive banners. If not, enable them.
if ( !extraParameters[@"adaptive_banner"] )
{
[adView setExtraParameterForKey: @"adaptive_banner" value: @"true"];
}
}
// Handle initial extra parameters if publisher sets it before creating ad view
if ( extraParameters )
{
@@ -1430,6 +1378,8 @@ static ALUnityBackgroundCallback backgroundCallback;
}
else if ( [@"adaptive_banner" isEqualToString: key] )
{
[self log: @"Setting adaptive banners via extra parameters is deprecated and will be removed in a future plugin version. Use the CreateBanner(adUnitIdentifier, AdViewConfiguration) API to properly configure adaptive banners."];
BOOL shouldUseAdaptiveBanner = [NSNumber al_numberWithString: value].boolValue;
if ( shouldUseAdaptiveBanner )
{
@@ -1442,6 +1392,18 @@ static ALUnityBackgroundCallback backgroundCallback;
[self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
}
else if ( [@"ignore_safe_area_landscape" isEqualToString: key] && [NSNumber al_numberWithString: value].boolValue )
{
[self.ignoreSafeAreaLandscapeAdUnitIdentifiers addObject: adUnitIdentifier];
[self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
}
}
if ( [adFormat isAdViewAd] && [@"clips_to_bounds" isEqualToString: key] )
{
BOOL clipsToBounds = [NSNumber al_numberWithString: value].boolValue;
MAAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat];
view.clipsToBounds = clipsToBounds;
}
}
@@ -1544,6 +1506,8 @@ static ALUnityBackgroundCallback backgroundCallback;
- (void)log:(NSString *)format, ...
{
if (max_unity_should_disable_all_logs()) return;
va_list valist;
va_start(valist, format);
NSString *message = [[NSString alloc] initWithFormat: format arguments: valist];
@@ -1554,6 +1518,8 @@ static ALUnityBackgroundCallback backgroundCallback;
+ (void)log:(NSString *)format, ...
{
if (max_unity_should_disable_all_logs()) return;
va_list valist;
va_start(valist, format);
NSString *message = [[NSString alloc] initWithFormat: format arguments: valist];
@@ -1567,10 +1533,11 @@ static ALUnityBackgroundCallback backgroundCallback;
MAInterstitialAd *result = self.interstitials[adUnitIdentifier];
if ( !result )
{
result = [[MAInterstitialAd alloc] initWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk];
result = [[MAInterstitialAd alloc] initWithAdUnitIdentifier: adUnitIdentifier];
result.delegate = self;
result.revenueDelegate = self;
result.adReviewDelegate = self;
result.expirationDelegate = self;
self.interstitials[adUnitIdentifier] = result;
}
@@ -1583,9 +1550,10 @@ static ALUnityBackgroundCallback backgroundCallback;
MAAppOpenAd *result = self.appOpenAds[adUnitIdentifier];
if ( !result )
{
result = [[MAAppOpenAd alloc] initWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk];
result = [[MAAppOpenAd alloc] initWithAdUnitIdentifier: adUnitIdentifier];
result.delegate = self;
result.revenueDelegate = self;
result.expirationDelegate = self;
self.appOpenAds[adUnitIdentifier] = result;
}
@@ -1598,10 +1566,11 @@ static ALUnityBackgroundCallback backgroundCallback;
MARewardedAd *result = self.rewardedAds[adUnitIdentifier];
if ( !result )
{
result = [MARewardedAd sharedWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk];
result = [MARewardedAd sharedWithAdUnitIdentifier: adUnitIdentifier];
result.delegate = self;
result.revenueDelegate = self;
result.adReviewDelegate = self;
result.expirationDelegate = self;
self.rewardedAds[adUnitIdentifier] = result;
}
@@ -1609,34 +1578,35 @@ static ALUnityBackgroundCallback backgroundCallback;
return result;
}
- (MARewardedInterstitialAd *)retrieveRewardedInterstitialAdForAdUnitIdentifier:(NSString *)adUnitIdentifier
{
MARewardedInterstitialAd *result = self.rewardedInterstitialAds[adUnitIdentifier];
if ( !result )
{
result = [[MARewardedInterstitialAd alloc] initWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk];
result.delegate = self;
result.revenueDelegate = self;
result.adReviewDelegate = self;
self.rewardedInterstitialAds[adUnitIdentifier] = result;
}
return result;
}
- (MAAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat
{
return [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: nil withOffset: CGPointZero];
return [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: nil withOffset: CGPointZero isAdaptive: YES];
}
- (MAAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset
- (MAAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset isAdaptive:(BOOL)isAdaptive
{
MAAdView *result = self.adViews[adUnitIdentifier];
if ( !result && adViewPosition )
{
result = [[MAAdView alloc] initWithAdUnitIdentifier: adUnitIdentifier adFormat: adFormat sdk: self.sdk];
MAAdViewConfiguration *config = [MAAdViewConfiguration configurationWithBuilderBlock:^(MAAdViewConfigurationBuilder *builder) {
// Set adaptive type only for banner ads. If adaptive is enabled, use ANCHORED; otherwise, fall back to NONE.
if ( [adFormat isBannerOrLeaderAd] )
{
if ( isAdaptive )
{
builder.adaptiveType = MAAdViewAdaptiveTypeAnchored;
}
else
{
builder.adaptiveType = MAAdViewAdaptiveTypeNone;
[self.disabledAdaptiveBannerAdUnitIdentifiers addObject:adUnitIdentifier];
}
}
}];
// There is a Unity bug where if an empty UIView is on screen with user interaction enabled, and a user interacts with it, it just passes the events to random parts of the screen.
result = [[MAAdView alloc] initWithAdUnitIdentifier:adUnitIdentifier adFormat:adFormat configuration:config];
result.userInteractionEnabled = NO;
result.translatesAutoresizingMaskIntoConstraints = NO;
result.delegate = self;
@@ -1733,6 +1703,8 @@ static ALUnityBackgroundCallback backgroundCallback;
NSMutableArray<NSLayoutConstraint *> *constraints = [NSMutableArray arrayWithObject: [adView.heightAnchor constraintEqualToConstant: adViewSize.height]];
UILayoutGuide *layoutGuide = superview.safeAreaLayoutGuide;
BOOL shouldIgnoreSafeArea = [self shouldIgnoreSafeAreaForAdUnitIdentifier: adUnitIdentifier];
NSLayoutAnchor *topAnchor = shouldIgnoreSafeArea ? superview.topAnchor : layoutGuide.topAnchor;
if ( [adViewPosition isEqual: @"top_center"] || [adViewPosition isEqual: @"bottom_center"] )
{
@@ -1763,7 +1735,7 @@ static ALUnityBackgroundCallback backgroundCallback;
if ( [adViewPosition isEqual: @"top_center"] )
{
[constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor],
[constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: topAnchor],
[self.safeAreaBackground.topAnchor constraintEqualToAnchor: superview.topAnchor],
[self.safeAreaBackground.bottomAnchor constraintEqualToAnchor: adView.topAnchor]]];
}
@@ -1783,7 +1755,7 @@ static ALUnityBackgroundCallback backgroundCallback;
if ( [adViewPosition isEqual: @"top_center"] )
{
[constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor],
[constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: topAnchor],
[self.safeAreaBackground.topAnchor constraintEqualToAnchor: superview.topAnchor],
[self.safeAreaBackground.bottomAnchor constraintEqualToAnchor: adView.topAnchor]]];
}
@@ -1805,7 +1777,7 @@ static ALUnityBackgroundCallback backgroundCallback;
if ( [adViewPosition isEqual: @"top_center"] )
{
[constraints addObject: [adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor]];
[constraints addObject: [adView.topAnchor constraintEqualToAnchor: topAnchor]];
}
else // BottomCenter
{
@@ -1922,11 +1894,11 @@ static ALUnityBackgroundCallback backgroundCallback;
if ( [adViewPosition isEqual: @"top_left"] )
{
[constraints addObjectsFromArray: @[[adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor constant: adViewOffset.x],
[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor constant: adViewOffset.y]]];
[adView.topAnchor constraintEqualToAnchor: topAnchor constant: adViewOffset.y]]];
}
else if ( [adViewPosition isEqual: @"top_right"] )
{
[constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor],
[constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: topAnchor],
[adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor]]];
}
else if ( [adViewPosition isEqual: @"centered"] )
@@ -2015,6 +1987,8 @@ static ALUnityBackgroundCallback backgroundCallback;
- (NSString *)requestLatencyMillisFromRequestLatency:(NSTimeInterval)requestLatency
{
if ( requestLatency == -1 ) return @"-1";
// Convert latency from seconds to milliseconds to match Android.
long requestLatencyMillis = requestLatency * 1000;
return @(requestLatencyMillis).stringValue;
@@ -2081,4 +2055,15 @@ static ALUnityBackgroundCallback backgroundCallback;
}
}
#pragma mark - Helper
- (BOOL)shouldIgnoreSafeAreaForAdUnitIdentifier:(NSString *)adUnitIdentifier
{
if ( ![self.ignoreSafeAreaLandscapeAdUnitIdentifiers containsObject: adUnitIdentifier] ) return NO;
// We should use the superview instead of layout guide if the application's orientation is landscape and the extra parameter is set
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
return orientation == UIInterfaceOrientationLandscapeRight || orientation == UIInterfaceOrientationLandscapeLeft;
}
@end
@@ -8,12 +8,13 @@
#import "MAUnityAdManager.h"
#define VERSION @"7.0.0"
#define VERSION @"8.6.3"
#define NSSTRING(_X) ( (_X != NULL) ? [NSString stringWithCString: _X encoding: NSStringEncodingConversionAllowLossy].al_stringByTrimmingWhitespace : nil)
@interface NSString (ALUtils)
@property (nonatomic, copy, readonly) NSString *al_stringByTrimmingWhitespace;
@property (assign, readonly, getter=al_isValidString) BOOL al_validString;
- (BOOL)al_isEqualToStringIgnoringCase:(nullable NSString *)otherString;
@end
@interface ALSdkInitializationConfigurationBuilder (ALUtils)
@@ -35,11 +36,13 @@ extern "C"
static bool _isSdkInitialized = false;
static bool _initializeSdkCalled = false;
static bool _disableAllLogs = false;
// Helper method to create C string copy
static const char * cStringCopy(NSString *string);
// Helper method to log errors
void logUninitializedAccessError(const char *callingMethod);
void max_unity_log_uninitialized_access_error(const char *callingMethod);
void max_unity_log_error(NSString *message);
ALSdk *getSdk()
{
@@ -72,6 +75,11 @@ extern "C"
return _initConfigurationBuilder;
}
bool max_unity_should_disable_all_logs()
{
return _disableAllLogs;
}
int getConsentStatusValue(NSNumber *consentStatus)
{
if ( consentStatus )
@@ -182,13 +190,14 @@ extern "C"
NSArray<MAMediatedNetworkInfo *> *availableMediatedNetworks = [getSdk() availableMediatedNetworks];
// Create array of serialized network strings
NSMutableArray<NSDictionary<NSString *, NSString *> *> *serializedNetworks = [NSMutableArray arrayWithCapacity: availableMediatedNetworks.count];
NSMutableArray<NSDictionary<NSString *, id> *> *serializedNetworks = [NSMutableArray arrayWithCapacity: availableMediatedNetworks.count];
for ( MAMediatedNetworkInfo *mediatedNetwork in availableMediatedNetworks )
{
NSDictionary<NSString *, NSString *> *mediatedNetworkDictionary = @{@"name" : mediatedNetwork.name,
NSDictionary<NSString *, id> *mediatedNetworkDictionary = @{@"name" : mediatedNetwork.name,
@"adapterClassName" : mediatedNetwork.adapterClassName,
@"adapterVersion" : mediatedNetwork.adapterVersion,
@"sdkVersion" : mediatedNetwork.sdkVersion};
@"sdkVersion" : mediatedNetwork.sdkVersion,
@"initializationStatus" : @(mediatedNetwork.initializationStatus)};
[serializedNetworks addObject: mediatedNetworkDictionary];
}
@@ -200,7 +209,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
NSLog(@"[%@] Failed to show mediation debugger - please ensure the AppLovin MAX Unity Plugin has been initialized by calling 'MaxSdk.InitializeSdk();'!", TAG);
max_unity_log_error(@"Failed to show mediation debugger - please ensure the AppLovin MAX Unity Plugin has been initialized by calling 'MaxSdk.InitializeSdk();'!");
return;
}
@@ -211,25 +220,13 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
NSLog(@"[%@] Failed to show creative debugger - please ensure the AppLovin MAX Unity Plugin has been initialized by calling 'MaxSdk.InitializeSdk();'!", TAG);
max_unity_log_error(@"Failed to show creative debugger - please ensure the AppLovin MAX Unity Plugin has been initialized by calling 'MaxSdk.InitializeSdk();'!");
return;
}
[getSdk() showCreativeDebugger];
}
void _MaxShowConsentDialog()
{
NSLog(@"[%@] Failed to show consent dialog - Unavailable on iOS, please use the consent flow: https://developers.applovin.com/en/unity/overview/terms-and-privacy-policy-flow", TAG);
}
int _MaxConsentDialogState()
{
if ( !_isSdkInitialized ) return ALConsentDialogStateUnknown;
return (int) getSdk().configuration.consentDialogState;
}
void _MaxSetUserId(const char *userId)
{
getSdk().settings.userIdentifier = NSSTRING(userId);
@@ -239,7 +236,7 @@ extern "C"
{
if ( _initializeSdkCalled )
{
NSLog(@"[%@] Segment collection must be set before MAX SDK is initialized", TAG);
max_unity_log_error(@"Segment collection must be set before MAX SDK is initialized");
return;
}
@@ -250,7 +247,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxGetSdkConfiguration");
max_unity_log_uninitialized_access_error("_MaxGetSdkConfiguration");
return cStringCopy(@"");
}
@@ -296,33 +293,33 @@ extern "C"
return [ALPrivacySettings isDoNotSellSet];
}
void _MaxCreateBanner(const char *adUnitIdentifier, const char *bannerPosition)
void _MaxCreateBanner(const char *adUnitIdentifier, const char *bannerPosition, bool isAdaptive)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxCreateBanner");
max_unity_log_uninitialized_access_error("_MaxCreateBanner");
return;
}
[getAdManager() createBannerWithAdUnitIdentifier: NSSTRING(adUnitIdentifier) atPosition: NSSTRING(bannerPosition)];
[getAdManager() createBannerWithAdUnitIdentifier: NSSTRING(adUnitIdentifier) atPosition: NSSTRING(bannerPosition) isAdaptive: isAdaptive];
}
void _MaxCreateBannerXY(const char *adUnitIdentifier, const float x, const float y)
void _MaxCreateBannerXY(const char *adUnitIdentifier, const float x, const float y, bool isAdaptive)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxCreateBannerXY");
max_unity_log_uninitialized_access_error("_MaxCreateBannerXY");
return;
}
[getAdManager() createBannerWithAdUnitIdentifier: NSSTRING(adUnitIdentifier) x: x y: y];
[getAdManager() createBannerWithAdUnitIdentifier: NSSTRING(adUnitIdentifier) x: x y: y isAdaptive: isAdaptive];
}
void _MaxLoadBanner(const char *adUnitIdentifier)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxLoadBanner");
max_unity_log_uninitialized_access_error("_MaxLoadBanner");
return;
}
@@ -333,7 +330,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerBackgroundColor");
max_unity_log_uninitialized_access_error("_MaxSetBannerBackgroundColor");
return;
}
@@ -344,7 +341,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerPlacement");
max_unity_log_uninitialized_access_error("_MaxSetBannerPlacement");
return;
}
@@ -355,7 +352,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxStartBannerAutoRefresh");
max_unity_log_uninitialized_access_error("_MaxStartBannerAutoRefresh");
return;
}
@@ -366,7 +363,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxStopBannerAutoRefresh");
max_unity_log_uninitialized_access_error("_MaxStopBannerAutoRefresh");
return;
}
@@ -377,7 +374,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetBannerExtraParameter");
return;
}
@@ -390,7 +387,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetBannerLocalExtraParameter");
return;
}
@@ -403,7 +400,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetBannerLocalExtraParameter");
return;
}
@@ -417,7 +414,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerCustomData");
max_unity_log_uninitialized_access_error("_MaxSetBannerCustomData");
return;
}
@@ -428,7 +425,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetBannerWidth");
max_unity_log_uninitialized_access_error("_MaxSetBannerWidth");
return;
}
@@ -439,7 +436,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxUpdateBannerPosition");
max_unity_log_uninitialized_access_error("_MaxUpdateBannerPosition");
return;
}
@@ -450,7 +447,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxUpdateBannerPositionXY");
max_unity_log_uninitialized_access_error("_MaxUpdateBannerPositionXY");
return;
}
@@ -461,7 +458,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowBanner");
max_unity_log_uninitialized_access_error("_MaxShowBanner");
return;
}
@@ -472,7 +469,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxDestroyBanner");
max_unity_log_uninitialized_access_error("_MaxDestroyBanner");
return;
}
@@ -483,7 +480,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxHideBanner");
max_unity_log_uninitialized_access_error("_MaxHideBanner");
return;
}
@@ -494,7 +491,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxGetBannerLayout");
max_unity_log_uninitialized_access_error("_MaxGetBannerLayout");
return cStringCopy(@"");
}
@@ -505,7 +502,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxCreateMRec");
max_unity_log_uninitialized_access_error("_MaxCreateMRec");
return;
}
@@ -516,7 +513,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxCreateMRecXY");
max_unity_log_uninitialized_access_error("_MaxCreateMRecXY");
return;
}
@@ -527,7 +524,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxLoadMRec");
max_unity_log_uninitialized_access_error("_MaxLoadMRec");
return;
}
@@ -538,7 +535,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetMRecPlacement");
max_unity_log_uninitialized_access_error("_MaxSetMRecPlacement");
return;
}
@@ -549,7 +546,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxStartMRecAutoRefresh");
max_unity_log_uninitialized_access_error("_MaxStartMRecAutoRefresh");
return;
}
@@ -560,7 +557,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxStopMRecAutoRefresh");
max_unity_log_uninitialized_access_error("_MaxStopMRecAutoRefresh");
return;
}
@@ -571,7 +568,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxUpdateMRecPosition");
max_unity_log_uninitialized_access_error("_MaxUpdateMRecPosition");
return;
}
@@ -582,7 +579,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxUpdateMRecPositionXY");
max_unity_log_uninitialized_access_error("_MaxUpdateMRecPositionXY");
return;
}
@@ -593,7 +590,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowMRec");
max_unity_log_uninitialized_access_error("_MaxShowMRec");
return;
}
@@ -604,7 +601,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxDestroyMRec");
max_unity_log_uninitialized_access_error("_MaxDestroyMRec");
return;
}
@@ -615,7 +612,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxHideMRec");
max_unity_log_uninitialized_access_error("_MaxHideMRec");
return;
}
@@ -626,7 +623,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetMRecExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetMRecExtraParameter");
return;
}
@@ -639,7 +636,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetMRecLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetMRecLocalExtraParameter");
return;
}
@@ -652,7 +649,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetMRecLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetMRecLocalExtraParameter");
return;
}
@@ -666,7 +663,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetMRecCustomData");
max_unity_log_uninitialized_access_error("_MaxSetMRecCustomData");
return;
}
@@ -677,7 +674,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxGetMRecLayout");
max_unity_log_uninitialized_access_error("_MaxGetMRecLayout");
return cStringCopy(@"");
}
@@ -688,7 +685,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxLoadInterstitial");
max_unity_log_uninitialized_access_error("_MaxLoadInterstitial");
return;
}
@@ -699,7 +696,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetInterstitialExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetInterstitialExtraParameter");
return;
}
@@ -712,7 +709,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetInterstitialLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetInterstitialLocalExtraParameter");
return;
}
@@ -725,7 +722,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetInterstitialLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetInterstitialLocalExtraParameter");
return;
}
@@ -739,7 +736,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxIsInterstitialReady");
max_unity_log_uninitialized_access_error("_MaxIsInterstitialReady");
return false;
}
@@ -750,7 +747,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowInterstitial");
max_unity_log_uninitialized_access_error("_MaxShowInterstitial");
return;
}
@@ -761,7 +758,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxLoadAppOpenAd");
max_unity_log_uninitialized_access_error("_MaxLoadAppOpenAd");
return;
}
@@ -772,7 +769,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetAppOpenAdExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetAppOpenAdExtraParameter");
return;
}
@@ -785,7 +782,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetAppOpenAdLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetAppOpenAdLocalExtraParameter");
return;
}
@@ -798,7 +795,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetAppOpenAdLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetAppOpenAdLocalExtraParameter");
return;
}
@@ -812,7 +809,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxIsAppOpenAdReady");
max_unity_log_uninitialized_access_error("_MaxIsAppOpenAdReady");
return false;
}
@@ -823,7 +820,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowAppOpenAd");
max_unity_log_uninitialized_access_error("_MaxShowAppOpenAd");
return;
}
@@ -834,7 +831,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxLoadRewardedAd");
max_unity_log_uninitialized_access_error("_MaxLoadRewardedAd");
return;
}
@@ -845,7 +842,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetRewardedAdExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetRewardedAdExtraParameter");
return;
}
@@ -858,7 +855,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetRewardedAdLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetRewardedAdLocalExtraParameter");
return;
}
@@ -871,7 +868,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetRewardedAdLocalExtraParameter");
max_unity_log_uninitialized_access_error("_MaxSetRewardedAdLocalExtraParameter");
return;
}
@@ -885,7 +882,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxIsRewardedAdReady");
max_unity_log_uninitialized_access_error("_MaxIsRewardedAdReady");
return false;
}
@@ -896,91 +893,18 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowRewardedAd");
max_unity_log_uninitialized_access_error("_MaxShowRewardedAd");
return;
}
[getAdManager() showRewardedAdWithAdUnitIdentifier: NSSTRING(adUnitIdentifier) placement: NSSTRING(placement) customData: NSSTRING(customData)];
}
void _MaxLoadRewardedInterstitialAd(const char *adUnitIdentifier)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxLoadRewardedInterstitialAd");
return;
}
[getAdManager() loadRewardedInterstitialAdWithAdUnitIdentifier: NSSTRING(adUnitIdentifier)];
}
void _MaxSetRewardedInterstitialAdExtraParameter(const char *adUnitIdentifier, const char *key, const char *value)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetRewardedInterstitialAdExtraParameter");
return;
}
[getAdManager() setRewardedInterstitialAdExtraParameterForAdUnitIdentifier: NSSTRING(adUnitIdentifier)
key: NSSTRING(key)
value: NSSTRING(value)];
}
void _MaxSetRewardedInterstitialAdLocalExtraParameter(const char *adUnitIdentifier, const char *key, MAUnityRef value)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetRewardedInterstitialAdLocalExtraParameter");
return;
}
[getAdManager() setRewardedInterstitialAdLocalExtraParameterForAdUnitIdentifier: NSSTRING(adUnitIdentifier)
key: NSSTRING(key)
value: (__bridge id)value];
}
void _MaxSetRewardedInterstitialAdLocalExtraParameterJSON(const char *adUnitIdentifier, const char *key, const char *json)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxSetRewardedInterstitialAdLocalExtraParameter");
return;
}
id value = getLocalExtraParameterValue(json);
[getAdManager() setRewardedInterstitialAdLocalExtraParameterForAdUnitIdentifier: NSSTRING(adUnitIdentifier)
key: NSSTRING(key)
value: value];
}
bool _MaxIsRewardedInterstitialAdReady(const char *adUnitIdentifier)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxIsRewardedInterstitialAdReady");
return false;
}
return [getAdManager() isRewardedInterstitialAdReadyWithAdUnitIdentifier: NSSTRING(adUnitIdentifier)];
}
void _MaxShowRewardedInterstitialAd(const char *adUnitIdentifier, const char *placement, const char *customData)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowRewardedInterstitialAd");
return;
}
[getAdManager() showRewardedInterstitialAdWithAdUnitIdentifier: NSSTRING(adUnitIdentifier) placement: NSSTRING(placement) customData: NSSTRING(customData)];
}
void _MaxTrackEvent(const char *event, const char *parameters)
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxTrackEvent");
max_unity_log_uninitialized_access_error("_MaxTrackEvent");
return;
}
@@ -1042,11 +966,6 @@ extern "C"
return [UIScreen.mainScreen nativeScale];
}
const char * _MaxGetAdInfo(const char *adUnitIdentifier)
{
return cStringCopy([getAdManager() adInfoForAdUnitIdentifier: NSSTRING(adUnitIdentifier)]);
}
const char * _MaxGetAdValue(const char *adUnitIdentifier, const char *key)
{
return cStringCopy([getAdManager() adValueForAdUnitIdentifier: NSSTRING(adUnitIdentifier) withKey: NSSTRING(key)]);
@@ -1066,7 +985,7 @@ extern "C"
{
if ( _initializeSdkCalled )
{
NSLog(@"[%@] Test device advertising IDs must be set before MAX SDK is initialized", TAG);
max_unity_log_error(@"Test device advertising IDs must be set before MAX SDK is initialized");
return;
}
@@ -1083,7 +1002,7 @@ extern "C"
{
if ( _initializeSdkCalled )
{
NSLog(@"[%@] Exception handler must be enabled/disabled before MAX SDK is initialized", TAG);
max_unity_log_error(@"Exception handler must be enabled/disabled before MAX SDK is initialized");
return;
}
@@ -1095,12 +1014,19 @@ extern "C"
NSString *stringKey = NSSTRING(key);
if ( ![stringKey al_isValidString] )
{
NSLog(@"[%@] Failed to set extra parameter for nil or empty key: %@", TAG, stringKey);
NSString *message = [NSString stringWithFormat:@"Failed to set extra parameter for nil or empty key: %@", stringKey];
max_unity_log_error(message);
return;
}
NSString *stringValue = NSSTRING(value);
if ( [@"disable_all_logs" isEqualToString: stringKey] )
{
_disableAllLogs = [@"true" al_isEqualToStringIgnoringCase: stringValue];
}
ALSdkSettings *settings = getSdk().settings;
[settings setExtraParameterForKey: stringKey value: NSSTRING(value)];
[settings setExtraParameterForKey: stringKey value: stringValue];
}
int * _MaxGetSafeAreaInsets()
@@ -1114,7 +1040,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxShowCmpForExistingUser");
max_unity_log_uninitialized_access_error("_MaxShowCmpForExistingUser");
return;
}
@@ -1125,7 +1051,7 @@ extern "C"
{
if ( !_initializeSdkCalled )
{
logUninitializedAccessError("_MaxHasSupportedCmp");
max_unity_log_uninitialized_access_error("_MaxHasSupportedCmp");
return false;
}
@@ -1137,9 +1063,18 @@ extern "C"
return [MAUnityAdManager adaptiveBannerHeightForWidth: width];
}
void logUninitializedAccessError(const char *callingMethod)
void max_unity_log_uninitialized_access_error(const char *callingMethod)
{
NSLog(@"[%@] Failed to execute: %s - please ensure the AppLovin MAX Unity Plugin has been initialized by calling 'MaxSdk.InitializeSdk();'!", TAG, callingMethod);
NSString *message = [NSString stringWithFormat:@"Failed to execute: %s - please ensure the AppLovin MAX Unity Plugin has been initialized by calling 'MaxSdk.InitializeSdk();'!", callingMethod];
max_unity_log_error(message);
}
void max_unity_log_error(NSString *message)
{
if (_disableAllLogs) return;
NSString *logMessage = [NSString stringWithFormat: @"[%@] %@", TAG, message];
NSLog(@"%@", logMessage);
}
}
@@ -1,3 +1,3 @@
# AppLovin MAX Unity Plugin
To get the latest changes, see the [AppLovin MAX Unity Changelog](https://developers.applovin.com/en/unity/changelog).
To get the latest changes, see the [AppLovin MAX Unity Changelog](https://support.axon.ai/en/max/unity/changelog).
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 8a6ad2e147c3a4553bda59a84413f07b
guid: dfb944c2e1cb479cabf023040a5942d2
labels:
- al_max
- al_max_export_path-MaxSdk/Version.md
- al_max_export_path-MaxSdk/CHANGELOG.md
TextScriptImporter:
externalObjects: {}
userData:
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:bigoads-adapter:4.9.1.0" />
<androidPackage spec="com.applovin.mediation:bigoads-adapter:5.9.0.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationBigoAdsAdapter" version="4.5.1.0" />
<iosPod name="AppLovinMediationBigoAdsAdapter" version="5.2.0.0" />
</iosPods>
</dependencies>
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:bytedance-adapter:6.3.0.3.0">
<androidPackage spec="com.applovin.mediation:bytedance-adapter:8.0.0.5.0">
<repositories>
<repository>https://artifact.bytedance.com/repository/pangle</repository>
</repositories>
</androidPackage>
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationByteDanceAdapter" version="6.3.0.6.0" />
<iosPod name="AppLovinMediationByteDanceAdapter" version="8.0.1.0.0" />
</iosPods>
</dependencies>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:chartboost-adapter:9.7.0.4">
<androidPackage spec="com.applovin.mediation:chartboost-adapter:9.12.0.0">
<repositories>
<repository>https://cboost.jfrog.io/artifactory/chartboost-ads/</repository>
</repositories>
@@ -9,6 +9,6 @@
<androidPackage spec="com.google.android.gms:play-services-base:16.1.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationChartboostAdapter" version="9.7.0.2" />
<iosPod name="AppLovinMediationChartboostAdapter" version="9.12.0.0" />
</iosPods>
</dependencies>
@@ -5,9 +5,9 @@
Since FAN SDK depends on older versions of a few support and play service versions
`com.applovin.mediation:facebook-adapter:x.y.z.a` resolves to `com.applovin.mediation:facebook-adapter:+` which pulls down the beta versions of FAN SDK.
Note that forcing the adapter is enough to stop Jar Resolver from pulling the latest FAN SDK. -->
<androidPackage spec="com.applovin.mediation:facebook-adapter:[6.18.0.1]" />
<androidPackage spec="com.applovin.mediation:facebook-adapter:[6.21.0.0]" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationFacebookAdapter" version="6.15.2.1" />
<iosPod name="AppLovinMediationFacebookAdapter" version="6.21.1.0" />
</iosPods>
</dependencies>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:fyber-adapter:8.3.2.0"/>
<androidPackage spec="com.applovin.mediation:fyber-adapter:8.4.5.0"/>
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationFyberAdapter" version="8.3.2.1"/>
<iosPod name="AppLovinMediationFyberAdapter" version="8.4.7.0"/>
</iosPods>
</dependencies>
@@ -2,9 +2,9 @@
<dependencies>
<androidPackages>
<!-- Ensure that Resolver doesn't inadvertently pull the latest Play Services Ads' SDK that we haven't certified against. -->
<androidPackage spec="com.applovin.mediation:google-adapter:[24.5.0.0]" />
<androidPackage spec="com.applovin.mediation:google-adapter:[25.3.0.0]" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationGoogleAdapter" version="12.9.0.0" />
<iosPod name="AppLovinMediationGoogleAdapter" version="13.4.0.0" />
</iosPods>
</dependencies>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:inmobi-adapter:10.7.8.1" />
<androidPackage spec="com.applovin.mediation:inmobi-adapter:11.3.0.0" />
<androidPackage spec="com.squareup.picasso:picasso:2.71828" />
<androidPackage spec="com.android.support:recyclerview-v7:28.+" />
<androidPackage spec="com.android.support:customtabs:28.+" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationInMobiAdapter" version="10.7.8.0" />
<iosPod name="AppLovinMediationInMobiAdapter" version="11.3.0.0" />
</iosPods>
</dependencies>
@@ -1,13 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:ironsource-adapter:8.4.0.0.1">
<repositories>
<repository>https://android-sdk.is.com/</repository>
</repositories>
</androidPackage>
<androidPackage spec="com.applovin.mediation:ironsource-adapter:9.4.2.0.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationIronSourceAdapter" version="8.4.0.0.0" />
<iosPod name="AppLovinMediationIronSourceAdapter" version="9.4.1.0.0" />
</iosPods>
</dependencies>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:mintegral-adapter:16.8.61.1">
<androidPackage spec="com.applovin.mediation:mintegral-adapter:17.1.61.0">
<repositories>
<repository>https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea</repository>
</repositories>
@@ -9,6 +9,6 @@
<androidPackage spec="androidx.recyclerview:recyclerview:1.2.1" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationMintegralAdapter" version="7.7.3.0.0" />
<iosPod name="AppLovinMediationMintegralAdapter" version="8.1.3.0.0" />
</iosPods>
</dependencies>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:moloco-adapter:4.0.0.0" />
<androidPackage spec="com.applovin.mediation:moloco-adapter:4.8.1.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationMolocoAdapter" version="3.13.0.0" />
<iosPod name="AppLovinMediationMolocoAdapter" version="4.6.1.1" />
</iosPods>
</dependencies>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:unityads-adapter:4.12.4.0" />
<androidPackage spec="com.applovin.mediation:unityads-adapter:4.18.0.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationUnityAdsAdapter" version="4.12.4.0" />
<iosPod name="AppLovinMediationUnityAdsAdapter" version="4.18.0.0" />
</iosPods>
</dependencies>
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:verve-adapter:3.6.1.0"/>
<androidPackage spec="com.applovin.mediation:verve-adapter:3.8.1.0"/>
<repositories>
<repository>https://verve.jfrog.io/artifactory/verve-gradle-release</repository>
</repositories>
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationVerveAdapter" version="3.6.1.0"/>
<iosPod name="AppLovinMediationVerveAdapter" version="3.8.1.0"/>
</iosPods>
</dependencies>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:vungle-adapter:7.4.1.3" />
<androidPackage spec="com.applovin.mediation:vungle-adapter:7.7.4.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationVungleAdapter" version="7.4.2.0" />
<iosPod name="AppLovinMediationVungleAdapter" version="7.7.3.0" />
</iosPods>
</dependencies>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="com.applovin.mediation:yandex-adapter:7.15.1.0" />
<androidPackage spec="com.applovin.mediation:yandex-adapter:8.0.0.0" />
</androidPackages>
<iosPods>
<iosPod name="AppLovinMediationYandexAdapter" version="7.15.1.0" />
<iosPod name="AppLovinMediationYandexAdapter" version="7.18.4.0" />
</iosPods>
</dependencies>
@@ -14,23 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
qualityServiceEnabled: 1
sdkKey: oXM0CzVDi7P1HstOpKvFMInPMOzpQ9uA6t3x75q5f5wQvsEy9vuiiiM94ZJCJSV7PcZGroSSInQCTGsu04QEiE
setAttributionReportEndpoint: 1
addApsSkAdNetworkIds: 0
customGradleVersionUrl:
customGradleToolsVersion:
consentFlowEnabled: 0
consentFlowPlatform: 0
consentFlowPrivacyPolicyUrl:
consentFlowTermsOfServiceUrl:
userTrackingUsageDescriptionEn:
userTrackingUsageLocalizationEnabled: 0
userTrackingUsageDescriptionDe:
userTrackingUsageDescriptionEs:
userTrackingUsageDescriptionFr:
userTrackingUsageDescriptionJa:
userTrackingUsageDescriptionKo:
userTrackingUsageDescriptionZhHans:
userTrackingUsageDescriptionZhHant:
adMobAndroidAppId:
adMobIosAppId: ca-app-pub-6857282130957360~6957421822
showInternalSettingsInIntegrationManager: 0

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 572fee4574afa4f6dbf2846e0c152fe8
guid: 4f2c9814b41bf2a4db96c9898dd87c40
labels:
- al_max
- al_max_export_path-MaxSdk/Resources/Images/alert_icon.png
- al_max_export_path-MaxSdk/Resources/Images/error_icon.png
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

@@ -0,0 +1,95 @@
fileFormatVersion: 2
guid: 036a0d1eebcb7467ba676e6cb67c7872
labels:
- al_max
- al_max_export_path-MaxSdk/Resources/Images/info_icon.png
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -16,7 +16,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// Handles auto updates for AppLovin MAX plugin.
/// </summary>
public class AppLovinAutoUpdater
public static class AppLovinAutoUpdater
{
public const string KeyAutoUpdateEnabled = "com.applovin.auto_update_enabled";
private const string KeyLastUpdateCheckTime = "com.applovin.last_update_check_time_v2"; // Updated to v2 to force adapter version checks in plugin version 3.1.10.
@@ -97,7 +97,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (option == 0) // Download
{
MaxSdkLogger.UserDebug("Downloading plugin...");
AppLovinIntegrationManager.downloadPluginProgressCallback = AppLovinIntegrationManagerWindow.OnDownloadPluginProgress;
AppLovinEditorCoroutine.StartCoroutine(AppLovinIntegrationManager.Instance.DownloadPlugin(data.AppLovinMax));
}
else if (option == 1) // Not Now
@@ -18,7 +18,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
///
/// TODO: Currently only supports shell (Linux). Add support for Windows machines.
/// </summary>
public class AppLovinCommandLine
public static class AppLovinCommandLine
{
/// <summary>
/// Result obtained by running a command line command.
@@ -6,10 +6,8 @@
// Copyright © 2019 AppLovin. All rights reserved.
//
using AppLovinMax.Scripts.IntegrationManager.Editor;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
@@ -21,6 +19,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
"AdColony",
"Criteo",
"LinkedIn",
"Nend",
"Snap",
"Tapjoy",
@@ -70,11 +69,26 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
"MaxSdk/Scripts/MaxVariableServiceUnityEditor.cs",
"MaxSdk/Scripts/MaxVariableServiceUnityEditor.cs.meta",
// TODO: Add MaxTargetingData and MaxUserSegment when the plugin has enough traction.
// The `MaxSdk/Scripts/Editor` folder contents have been moved into `MaxSdk/Scripts/IntegrationManager/Editor`.
"MaxSdk/Version.md",
"MaxSdk/Version.md.meta",
// The alert_icon.png has been renamed to error_icon.png.
"MaxSdk/Resources/Images/alert_icon.png",
"MaxSdk/Resources/Images/alert_icon.png.meta",
// `TargetingData` has been removed and we no longer set `UserSegment` through the Unity Plugin.
"MaxSdk/Scripts/MaxUserSegment.cs",
"MaxSdk/Scripts/MaxUserSegment.cs.meta",
"MaxSdk/Scripts/MaxTargetingData.cs",
"MaxSdk/Scripts/MaxTargetingData.cs.meta"
};
static AppLovinInitialize()
{
// Don't run obsolete file cleanup logic when entering play mode.
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
#if UNITY_IOS
// Check that the publisher is targeting iOS 9.0+
if (!PlayerSettings.iOS.targetOSVersionString.StartsWith("9.") && !PlayerSettings.iOS.targetOSVersionString.StartsWith("1"))
@@ -83,32 +97,22 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
#endif
var pluginParentDir = AppLovinIntegrationManager.PluginParentDirectory;
var isPluginOutsideAssetsDir = AppLovinIntegrationManager.IsPluginOutsideAssetsDirectory;
var changesMade = AppLovinIntegrationManager.MovePluginFilesIfNeeded(pluginParentDir, isPluginOutsideAssetsDir);
if (isPluginOutsideAssetsDir)
var isPluginInPackageManager = AppLovinIntegrationManager.IsPluginInPackageManager;
if (!isPluginInPackageManager)
{
// If the plugin is not under the assets folder, delete the MaxSdk/Mediation folder in the plugin, so that the adapters are not imported at that location and imported to the default location.
var mediationDir = Path.Combine(pluginParentDir, "MaxSdk/Mediation");
if (Directory.Exists(mediationDir))
{
FileUtil.DeleteFileOrDirectory(mediationDir);
FileUtil.DeleteFileOrDirectory(mediationDir + ".meta");
changesMade = true;
}
}
var changesMade = false;
foreach (var obsoleteFileExportPathToDelete in ObsoleteFileExportPathsToDelete)
{
var pathToDelete = MaxSdkUtils.GetAssetPathForExportPath(obsoleteFileExportPathToDelete);
if (CheckExistence(pathToDelete))
{
MaxSdkLogger.UserDebug("Deleting obsolete file '" + pathToDelete + "' that are no longer needed.");
MaxSdkLogger.UserDebug("Deleting obsolete file '" + pathToDelete + "' that is no longer needed.");
FileUtil.DeleteFileOrDirectory(pathToDelete);
changesMade = true;
}
}
var pluginParentDir = AppLovinIntegrationManager.PluginParentDirectory;
// Check if any obsolete networks are installed
foreach (var obsoleteNetwork in ObsoleteNetworks)
{
@@ -117,6 +121,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
MaxSdkLogger.UserDebug("Deleting obsolete network " + obsoleteNetwork + " from path " + networkDir + "...");
FileUtil.DeleteFileOrDirectory(networkDir);
FileUtil.DeleteFileOrDirectory(networkDir + ".meta");
changesMade = true;
}
}
@@ -125,7 +130,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (changesMade)
{
AssetDatabase.Refresh();
MaxSdkLogger.UserDebug("AppLovin MAX Migration completed");
MaxSdkLogger.UserDebug("Obsolete networks and files removed.");
}
}
AppLovinAutoUpdater.Update();
@@ -8,24 +8,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using AppLovinMax.Internal;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using VersionComparisonResult = MaxSdkUtils.VersionComparisonResult;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
[Serializable]
public class PluginData
{
// ReSharper disable InconsistentNaming - Consistent with JSON data.
public Network AppLovinMax;
public Network[] MediatedNetworks;
public Network[] PartnerMicroSdks;
public DynamicLibraryToEmbed[] ThirdPartyDynamicLibrariesToEmbed;
public Alert[] Alerts;
}
[Serializable]
@@ -48,21 +47,26 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
// }
//
// ReSharper disable InconsistentNaming - Consistent with JSON data.
public string Name;
public string DisplayName;
public string DownloadUrl;
public string DependenciesFilePath;
public PackageInfo[] Packages;
public string[] PluginFilePaths;
public Versions LatestVersions;
[NonSerialized] public Versions CurrentVersions;
[NonSerialized] public VersionComparisonResult CurrentToLatestVersionComparisonResult = VersionComparisonResult.Lesser;
[NonSerialized] public bool RequiresUpdate;
public DynamicLibraryToEmbed[] DynamicLibrariesToEmbed;
[NonSerialized] public Versions CurrentVersions;
[NonSerialized] public MaxSdkUtils.VersionComparisonResult CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Lesser;
[NonSerialized] public bool RequiresUpdate;
[NonSerialized] public bool IsCurrentlyInstalling;
}
[Serializable]
public class DynamicLibraryToEmbed
{
// ReSharper disable InconsistentNaming - Consistent with JSON data.
public string PodName;
public string[] FrameworkNames;
@@ -79,12 +83,62 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
}
public enum Severity
{
Info,
Warning,
Error
}
[Serializable]
public class Alert
{
public string SeverityType;
public string Title;
public string Message;
public string Url;
public string MinimumPluginVersion;
public string MaximumPluginVersion;
public string MinimumUnityVersion;
public string MaximumUnityVersion;
public Severity Severity;
public void InitializeSeverityEnum()
{
switch (SeverityType)
{
case "INFO":
Severity = Severity.Info;
break;
case "WARNING":
Severity = Severity.Warning;
break;
case "ERROR":
Severity = Severity.Error;
break;
default:
MaxSdkLogger.E(string.Format("Alert <{0}> has unsupported severity type <{1}>.", Title, SeverityType));
Severity = Severity.Info;
break;
}
}
public bool ShouldShowAlert()
{
var pluginVersionValid = MaxSdkUtils.IsVersionInRange(MaxSdk.Version, MinimumPluginVersion, MaximumPluginVersion);
var unityVersionValid = MaxSdkUtils.IsVersionInRange(Application.unityVersion, MinimumUnityVersion, MaximumUnityVersion);
return pluginVersionValid && unityVersionValid;
}
}
/// <summary>
/// A helper data class used to get current versions from Dependency.xml files.
/// </summary>
[Serializable]
public class Versions
{
// ReSharper disable InconsistentNaming - Consistent with JSON data.
public string Unity;
public string Android;
public string Ios;
@@ -108,66 +162,48 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
public override int GetHashCode()
{
return new {Unity, Android, Ios}.GetHashCode();
return new {unity = Unity, android = Android, ios = Ios}.GetHashCode();
}
private static string AdapterSdkVersion(string adapterVersion)
{
var index = adapterVersion.LastIndexOf(".");
if (string.IsNullOrEmpty(adapterVersion)) return "";
var index = adapterVersion.LastIndexOf(".", StringComparison.Ordinal);
return index > 0 ? adapterVersion.Substring(0, index) : adapterVersion;
}
}
/// <summary>
/// A manager class for MAX integration manager window.
///
/// TODO: Decide if we should namespace these classes.
/// </summary>
public class AppLovinIntegrationManager
{
/// <summary>
/// Delegate to be called when downloading a plugin with the progress percentage.
/// Delegate to be called when a plugin package's import is started.
/// </summary>
/// <param name="pluginName">The name of the plugin being downloaded.</param>
/// <param name="progress">Percentage downloaded.</param>
/// <param name="done">Whether or not the download is complete.</param>
public delegate void DownloadPluginProgressCallback(string pluginName, float progress, bool done);
internal delegate void ImportPackageStartedCallback(Network network);
/// <summary>
/// Delegate to be called when a plugin package is imported.
/// Delegate to be called when a plugin package is finished importing.
/// </summary>
/// <param name="network">The network data for which the package is imported.</param>
public delegate void ImportPackageCompletedCallback(Network network);
internal delegate void ImportPackageCompletedCallback(Network network);
private static readonly AppLovinIntegrationManager instance = new AppLovinIntegrationManager();
public static readonly string GradleTemplatePath = Path.Combine("Assets/Plugins/Android", "mainTemplate.gradle");
public static readonly string DefaultPluginExportPath = Path.Combine("Assets", "MaxSdk");
internal static readonly string GradleTemplatePath = Path.Combine("Assets/Plugins/Android", "mainTemplate.gradle");
private const string MaxSdkAssetExportPath = "MaxSdk/Scripts/MaxSdk.cs";
private const string MaxSdkMediationExportPath = "MaxSdk/Mediation";
internal static readonly string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}";
/// <summary>
/// Some publishers might re-export our plugin via Unity Package Manager and the plugin will not be under the Assets folder. This means that the mediation adapters, settings files should not be moved to the packages folder,
/// since they get overridden when the package is updated. These are the files that should not be moved, if the plugin is not under the Assets/ folder.
///
/// Note: When we distribute the plugin via Unity Package Manager, we need to distribute the adapters as separate packages, and the adapters won't be in the MaxSdk folder. So we need to take that into account.
/// </summary>
private static readonly List<string> PluginPathsToIgnoreMoveWhenPluginOutsideAssetsDirectory = new List<string>
{
"MaxSdk/Mediation",
"MaxSdk/Mediation.meta",
"MaxSdk/Resources.meta",
AppLovinSettings.SettingsExportPath,
AppLovinSettings.SettingsExportPath + ".meta"
};
private const string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}";
private static string externalDependencyManagerVersion;
public static DownloadPluginProgressCallback downloadPluginProgressCallback;
public static ImportPackageCompletedCallback importPackageCompletedCallback;
internal static ImportPackageStartedCallback OnImportPackageStartedCallback;
internal static ImportPackageCompletedCallback OnImportPackageCompletedCallback;
private UnityWebRequest webRequest;
private MaxWebRequest maxWebRequest;
private Network importingNetwork;
/// <summary>
@@ -185,8 +221,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
get
{
// Search for the asset with the default exported path first, In most cases, we should be able to find the asset.
// In some cases where we don't, use the platform specific export path to search for the asset (in case of migrating a project from Windows to Mac or vice versa).
// Search for the asset with the export path label.
// Paths are normalized using AltDirectorySeparatorChar (/) to ensure compatibility across platforms (in case of migrating a project from Windows to Mac or vice versa).
var maxSdkScriptAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkAssetExportPath);
// maxSdkScriptAssetPath will always have AltDirectorySeparatorChar (/) as the path separator. Convert to platform specific path.
@@ -195,21 +231,21 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
}
/// <summary>
/// When the base plugin is outside the <c>Assets/</c> directory, the mediation plugin files are still imported to the default location under <c>Assets/</c>.
/// Returns the parent directory where the mediation adapter plugins are imported.
/// </summary>
public static string MediationSpecificPluginParentDirectory
public static string MediationDirectory
{
get { return IsPluginOutsideAssetsDirectory ? "Assets" : PluginParentDirectory; }
get
{
var mediationAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkMediationExportPath);
return mediationAssetPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
}
/// <summary>
/// Whether or not the plugin is under the Assets/ folder.
/// Whether or not the plugin is in the Unity Package Manager.
/// </summary>
public static bool IsPluginOutsideAssetsDirectory
public static bool IsPluginInPackageManager
{
get { return !PluginParentDirectory.StartsWith("Assets"); }
get { return PluginParentDirectory.StartsWith("Packages"); }
}
/// <summary>
@@ -243,7 +279,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
get
{
if (!string.IsNullOrEmpty(externalDependencyManagerVersion)) return externalDependencyManagerVersion;
if (MaxSdkUtils.IsValidString(externalDependencyManagerVersion)) return externalDependencyManagerVersion;
try
{
@@ -263,14 +299,18 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private AppLovinIntegrationManager()
{
AssetDatabase.importPackageStarted += packageName =>
{
if (!IsImportingNetwork(packageName)) return;
CallImportPackageStartedCallback(importingNetwork);
};
// Add asset import callbacks.
AssetDatabase.importPackageCompleted += packageName =>
{
if (!IsImportingNetwork(packageName)) return;
var pluginParentDir = PluginParentDirectory;
var isPluginOutsideAssetsDir = IsPluginOutsideAssetsDirectory;
MovePluginFilesIfNeeded(pluginParentDir, isPluginOutsideAssetsDir);
AssetDatabase.Refresh();
CallImportPackageCompletedCallback(importingNetwork);
@@ -281,7 +321,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
if (!IsImportingNetwork(packageName)) return;
MaxSdkLogger.UserDebug("Package import cancelled.");
importingNetwork = null;
};
@@ -299,15 +338,15 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
public static PluginData LoadPluginDataSync()
{
var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
using (var unityWebRequest = UnityWebRequest.Get(url))
var webRequestConfig = new WebRequestConfig()
{
var operation = unityWebRequest.SendWebRequest();
EndPoint = url,
};
// Just wait till www is done
while (!operation.isDone) { }
var maxWebRequest = new MaxWebRequest(webRequestConfig);
var webResponse = maxWebRequest.SendSync();
return CreatePluginDataFromWebResponse(unityWebRequest);
}
return CreatePluginDataFromWebResponse(webResponse);
}
/// <summary>
@@ -317,25 +356,22 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
public IEnumerator LoadPluginData(Action<PluginData> callback)
{
var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
using (var unityWebRequest = UnityWebRequest.Get(url))
var webRequestConfig = new WebRequestConfig()
{
var operation = unityWebRequest.SendWebRequest();
while (!operation.isDone) yield return new WaitForSeconds(0.1f); // Just wait till www is done. Our coroutine is pretty rudimentary.
var pluginData = CreatePluginDataFromWebResponse(unityWebRequest);
EndPoint = url,
};
maxWebRequest = new MaxWebRequest(webRequestConfig);
yield return maxWebRequest.Send(webResponse =>
{
var pluginData = CreatePluginDataFromWebResponse(webResponse);
callback(pluginData);
}
});
}
private static PluginData CreatePluginDataFromWebResponse(UnityWebRequest unityWebRequest)
private static PluginData CreatePluginDataFromWebResponse(WebResponse webResponse)
{
#if UNITY_2020_1_OR_NEWER
if (unityWebRequest.result != UnityWebRequest.Result.Success)
#else
if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
#endif
if (!webResponse.IsSuccess)
{
MaxSdkLogger.E("Failed to load plugin data. Please check your internet connection.");
return null;
@@ -344,7 +380,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
PluginData pluginData;
try
{
pluginData = JsonUtility.FromJson<PluginData>(unityWebRequest.downloadHandler.text);
pluginData = JsonUtility.FromJson<PluginData>(webResponse.ResponseMessage);
AppLovinPackageManager.PluginData = pluginData;
}
catch (Exception exception)
{
@@ -356,87 +393,33 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
// Get current version of the plugin
var appLovinMax = pluginData.AppLovinMax;
UpdateCurrentVersions(appLovinMax, PluginParentDirectory);
AppLovinPackageManager.UpdateCurrentVersions(appLovinMax);
// Get current versions for all the mediation networks.
var mediationPluginParentDirectory = MediationSpecificPluginParentDirectory;
foreach (var network in pluginData.MediatedNetworks)
{
UpdateCurrentVersions(network, mediationPluginParentDirectory);
AppLovinPackageManager.UpdateCurrentVersions(network);
}
if (pluginData.PartnerMicroSdks != null)
{
foreach (var partnerMicroSdk in pluginData.PartnerMicroSdks)
{
UpdateCurrentVersions(partnerMicroSdk, mediationPluginParentDirectory);
AppLovinPackageManager.UpdateCurrentVersions(partnerMicroSdk);
}
}
if (pluginData.Alerts == null) return pluginData;
// Initiate Severity enums from the raw strings in the response
foreach (var alert in pluginData.Alerts)
{
alert.InitializeSeverityEnum();
}
return pluginData;
}
/// <summary>
/// Updates the CurrentVersion fields for a given network data object.
/// </summary>
/// <param name="network">Network for which to update the current versions.</param>
/// <param name="mediationPluginParentDirectory">The parent directory of where the mediation adapter plugins are imported to.</param>
public static void UpdateCurrentVersions(Network network, string mediationPluginParentDirectory)
{
var currentVersions = GetCurrentVersions(MaxSdkUtils.GetAssetPathForExportPath(network.DependenciesFilePath));
network.CurrentVersions = currentVersions;
// If AppLovin mediation plugin, get the version from MaxSdk and the latest and current version comparison.
if (network.Name.Equals("APPLOVIN_NETWORK"))
{
network.CurrentVersions.Unity = MaxSdk.Version;
var unityVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Unity, network.LatestVersions.Unity);
var androidVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Android, network.LatestVersions.Android);
var iosVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Ios, network.LatestVersions.Ios);
// Overall version is same if all the current and latest (from db) versions are same.
if (unityVersionComparison == VersionComparisonResult.Equal &&
androidVersionComparison == VersionComparisonResult.Equal &&
iosVersionComparison == VersionComparisonResult.Equal)
{
network.CurrentToLatestVersionComparisonResult = VersionComparisonResult.Equal;
}
// One of the installed versions is newer than the latest versions which means that the publisher is on a beta version.
else if (unityVersionComparison == VersionComparisonResult.Greater ||
androidVersionComparison == VersionComparisonResult.Greater ||
iosVersionComparison == VersionComparisonResult.Greater)
{
network.CurrentToLatestVersionComparisonResult = VersionComparisonResult.Greater;
}
// We have a new version available if all Android, iOS and Unity has a newer version available in db.
else
{
network.CurrentToLatestVersionComparisonResult = VersionComparisonResult.Lesser;
}
}
// For all other mediation adapters, get the version comparison using their Unity versions.
else
{
// If adapter is indeed installed, compare the current (installed) and the latest (from db) versions, so that we can determine if the publisher is on an older, current or a newer version of the adapter.
// If the publisher is on a newer version of the adapter than the db version, that means they are on a beta version.
if (!string.IsNullOrEmpty(currentVersions.Unity))
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.CompareUnityMediationVersions(currentVersions.Unity, network.LatestVersions.Unity);
}
if (!string.IsNullOrEmpty(network.CurrentVersions.Unity) && AppLovinAutoUpdater.MinAdapterVersions.ContainsKey(network.Name))
{
var comparisonResult = MaxSdkUtils.CompareUnityMediationVersions(network.CurrentVersions.Unity, AppLovinAutoUpdater.MinAdapterVersions[network.Name]);
// Requires update if current version is lower than the min required version.
network.RequiresUpdate = comparisonResult < 0;
}
else
{
// Reset value so that the Integration manager can hide the alert icon once adapter is updated.
network.RequiresUpdate = false;
}
}
}
/// <summary>
/// Downloads the plugin file for a given network.
/// </summary>
@@ -446,36 +429,25 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
public IEnumerator DownloadPlugin(Network network, bool showImport = true)
{
var path = Path.Combine(Application.temporaryCachePath, GetPluginFileName(network)); // TODO: Maybe delete plugin file after finishing import.
var downloadHandler = new DownloadHandlerFile(path);
webRequest = new UnityWebRequest(network.DownloadUrl)
var webRequestConfig = new WebRequestConfig()
{
method = UnityWebRequest.kHttpVerbGET,
downloadHandler = downloadHandler
DownloadHandler = new DownloadHandlerFile(path),
EndPoint = network.DownloadUrl
};
var operation = webRequest.SendWebRequest();
while (!operation.isDone)
maxWebRequest = new MaxWebRequest(webRequestConfig);
yield return maxWebRequest.Send(webResponse =>
{
yield return new WaitForSeconds(0.1f); // Just wait till webRequest is completed. Our coroutine is pretty rudimentary.
CallDownloadPluginProgressCallback(network.DisplayName, operation.progress, operation.isDone);
}
#if UNITY_2020_1_OR_NEWER
if (webRequest.result != UnityWebRequest.Result.Success)
#else
if (webRequest.isNetworkError || webRequest.isHttpError)
#endif
{
MaxSdkLogger.UserError(webRequest.error);
}
else
if (webResponse.IsSuccess)
{
importingNetwork = network;
AssetDatabase.ImportPackage(path, showImport);
}
webRequest.Dispose();
webRequest = null;
else
{
MaxSdkLogger.UserError("Failed to download plugin package: " + webResponse.ErrorMessage);
}
});
}
/// <summary>
@@ -483,9 +455,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
public void CancelDownload()
{
if (webRequest == null) return;
if (maxWebRequest == null) return;
webRequest.Abort();
maxWebRequest.Abort();
}
/// <summary>
@@ -503,111 +475,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
MaxSdkLogger.UserError(message);
}
/// <summary>
/// Checks whether or not an adapter with the given version or newer exists.
/// </summary>
/// <param name="adapterName">The name of the network (the root adapter folder name in "MaxSdk/Mediation/" folder.</param>
/// <param name="iosVersion">The min adapter version to check for. Can be <c>null</c> if we want to check for any version.</param>
/// <returns><c>true</c> if an adapter with the min version is installed.</returns>
public static bool IsAdapterInstalled(string adapterName, string iosVersion = null) // TODO: Add Android version check.
{
var dependencyFilePath = MaxSdkUtils.GetAssetPathForExportPath("MaxSdk/Mediation/" + adapterName + "/Editor/Dependencies.xml");
if (!File.Exists(dependencyFilePath)) return false;
// If version is null, we just need the adapter installed. We don't have to check for a specific version.
if (iosVersion == null) return true;
var currentVersion = GetCurrentVersions(dependencyFilePath);
var iosVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Ios, iosVersion);
return iosVersionComparison != VersionComparisonResult.Lesser;
}
#region Utility Methods
/// <summary>
/// Gets the current versions for a given network's dependency file path.
/// </summary>
/// <param name="dependencyPath">A dependency file path that from which to extract current versions.</param>
/// <returns>Current versions of a given network's dependency file.</returns>
public static Versions GetCurrentVersions(string dependencyPath)
{
XDocument dependency;
try
{
dependency = XDocument.Load(dependencyPath);
}
#pragma warning disable 0168
catch (IOException exception)
#pragma warning restore 0168
{
// Couldn't find the dependencies file. The plugin is not installed.
return new Versions();
}
// <dependencies>
// <androidPackages>
// <androidPackage spec="com.applovin.mediation:network_name-adapter:1.2.3.4" />
// </androidPackages>
// <iosPods>
// <iosPod name="AppLovinMediationNetworkNameAdapter" version="2.3.4.5" />
// </iosPods>
// </dependencies>
string androidVersion = null;
string iosVersion = null;
var dependenciesElement = dependency.Element("dependencies");
if (dependenciesElement != null)
{
var androidPackages = dependenciesElement.Element("androidPackages");
if (androidPackages != null)
{
var adapterPackage = androidPackages.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("androidPackage")
&& element.FirstAttribute.Name.LocalName.Equals("spec")
&& element.FirstAttribute.Value.StartsWith("com.applovin"));
if (adapterPackage != null)
{
androidVersion = adapterPackage.FirstAttribute.Value.Split(':').Last();
// Hack alert: Some Android versions might have square brackets to force a specific version. Remove them if they are detected.
if (androidVersion.StartsWith("["))
{
androidVersion = androidVersion.Trim('[', ']');
}
}
}
var iosPods = dependenciesElement.Element("iosPods");
if (iosPods != null)
{
var adapterPod = iosPods.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("iosPod")
&& element.FirstAttribute.Name.LocalName.Equals("name")
&& element.FirstAttribute.Value.StartsWith("AppLovin"));
if (adapterPod != null)
{
iosVersion = adapterPod.Attributes().First(attribute => attribute.Name.LocalName.Equals("version")).Value;
}
}
}
var currentVersions = new Versions();
if (androidVersion != null && iosVersion != null)
{
currentVersions.Unity = string.Format("android_{0}_ios_{1}", androidVersion, iosVersion);
currentVersions.Android = androidVersion;
currentVersions.Ios = iosVersion;
}
else if (androidVersion != null)
{
currentVersions.Unity = string.Format("android_{0}", androidVersion);
currentVersions.Android = androidVersion;
}
else if (iosVersion != null)
{
currentVersions.Unity = string.Format("ios_{0}", iosVersion);
currentVersions.Ios = iosVersion;
}
return currentVersions;
}
/// <summary>
/// Checks whether or not the given package name is the currently importing package.
/// </summary>
@@ -615,120 +484,22 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <returns>true if the importing package matches the given package name.</returns>
private bool IsImportingNetwork(string packageName)
{
// Note: The pluginName doesn't have the '.unitypacakge' extension included in its name but the pluginFileName does. So using Contains instead of Equals.
// Note: The pluginName doesn't have the '.unitypackage' extension included in its name but the pluginFileName does. So using Contains instead of Equals.
return importingNetwork != null && GetPluginFileName(importingNetwork).Contains(packageName);
}
/// <summary>
/// Moves the imported plugin files to the MaxSdk directory if the publisher has moved the plugin to a different directory. This is a failsafe for when some plugin files are not imported to the new location.
/// </summary>
/// <returns>True if the adapters have been moved.</returns>
public static bool MovePluginFilesIfNeeded(string pluginParentDirectory, bool isPluginOutsideAssetsDirectory)
private static void CallImportPackageStartedCallback(Network network)
{
var pluginDir = Path.Combine(pluginParentDirectory, "MaxSdk");
if (OnImportPackageStartedCallback == null) return;
// Check if the user has moved the Plugin and if new assets have been imported to the default directory.
if (DefaultPluginExportPath.Equals(pluginDir) || !Directory.Exists(DefaultPluginExportPath)) return false;
MovePluginFiles(DefaultPluginExportPath, pluginDir, isPluginOutsideAssetsDirectory);
if (!isPluginOutsideAssetsDirectory)
{
FileUtil.DeleteFileOrDirectory(DefaultPluginExportPath + ".meta");
}
AssetDatabase.Refresh();
return true;
}
/// <summary>
/// A helper function to move all the files recursively from the default plugin dir to a custom location the publisher moved the plugin to.
/// </summary>
private static void MovePluginFiles(string fromDirectory, string pluginRoot, bool isPluginOutsideAssetsDirectory)
{
var files = Directory.GetFiles(fromDirectory);
foreach (var file in files)
{
// We have to ignore some files, if the plugin is outside the Assets/ directory.
if (isPluginOutsideAssetsDirectory && PluginPathsToIgnoreMoveWhenPluginOutsideAssetsDirectory.Any(pluginPathsToIgnore => file.Contains(pluginPathsToIgnore))) continue;
// Check if the destination folder exists and create it if it doesn't exist
var parentDirectory = Path.GetDirectoryName(file);
var destinationDirectoryPath = parentDirectory.Replace(DefaultPluginExportPath, pluginRoot);
if (!Directory.Exists(destinationDirectoryPath))
{
Directory.CreateDirectory(destinationDirectoryPath);
}
// If the meta file is of a folder asset and doesn't have labels (it is auto generated by Unity), just delete it.
if (IsAutoGeneratedFolderMetaFile(file))
{
FileUtil.DeleteFileOrDirectory(file);
continue;
}
var destinationPath = file.Replace(DefaultPluginExportPath, pluginRoot);
// Check if the file is already present at the destination path and delete it.
if (File.Exists(destinationPath))
{
FileUtil.DeleteFileOrDirectory(destinationPath);
}
FileUtil.MoveFileOrDirectory(file, destinationPath);
}
var directories = Directory.GetDirectories(fromDirectory);
foreach (var directory in directories)
{
// We might have to ignore some directories, if the plugin is outside the Assets/ directory.
if (isPluginOutsideAssetsDirectory && PluginPathsToIgnoreMoveWhenPluginOutsideAssetsDirectory.Any(pluginPathsToIgnore => directory.Contains(pluginPathsToIgnore))) continue;
MovePluginFiles(directory, pluginRoot, isPluginOutsideAssetsDirectory);
}
if (!isPluginOutsideAssetsDirectory)
{
FileUtil.DeleteFileOrDirectory(fromDirectory);
}
}
private static bool IsAutoGeneratedFolderMetaFile(string assetPath)
{
// Check if it is a meta file.
if (!assetPath.EndsWith(".meta")) return false;
var lines = File.ReadAllLines(assetPath);
var isFolderAsset = false;
var hasLabels = false;
foreach (var line in lines)
{
if (line.Contains("folderAsset: yes"))
{
isFolderAsset = true;
}
if (line.Contains("labels:"))
{
hasLabels = true;
}
}
// If it is a folder asset and doesn't have a label, the meta file is auto generated by
return isFolderAsset && !hasLabels;
}
private static void CallDownloadPluginProgressCallback(string pluginName, float progress, bool isDone)
{
if (downloadPluginProgressCallback == null) return;
downloadPluginProgressCallback(pluginName, progress, isDone);
OnImportPackageStartedCallback(network);
}
private static void CallImportPackageCompletedCallback(Network network)
{
if (importPackageCompletedCallback == null) return;
if (OnImportPackageCompletedCallback == null) return;
importPackageCompletedCallback(network);
OnImportPackageCompletedCallback(network);
}
private static object GetEditorUserBuildSetting(string name, object defaultValue)
@@ -0,0 +1,80 @@
using System;
using System.Linq;
using UnityEngine;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
public static class AppLovinIntegrationManagerUtils
{
/// <summary>
/// Compares AppLovin MAX Unity mediation adapter plugin versions. Returns <see cref="MaxSdkUtils.VersionComparisonResult.Lesser"/>, <see cref="MaxSdkUtils.VersionComparisonResult.Equal"/>,
/// or <see cref="MaxSdkUtils.VersionComparisonResult.Greater"/> as the first version is less than, equal to, or greater than the second.
///
/// If a version for a specific platform is only present in one of the provided versions, the one that contains it is considered newer.
/// </summary>
/// <param name="versionA">The first version to be compared.</param>
/// <param name="versionB">The second version to be compared.</param>
/// <returns>
/// <see cref="MaxSdkUtils.VersionComparisonResult.Lesser"/> if versionA is less than versionB.
/// <see cref="MaxSdkUtils.VersionComparisonResult.Equal"/> if versionA and versionB are equal.
/// <see cref="MaxSdkUtils.VersionComparisonResult.Greater"/> if versionA is greater than versionB.
/// </returns>
internal static MaxSdkUtils.VersionComparisonResult CompareUnityMediationVersions(string versionA, string versionB)
{
if (versionA.Equals(versionB)) return MaxSdkUtils.VersionComparisonResult.Equal;
// Unity version would be of format: android_w.x.y.z_ios_a.b.c.d
// For Android only versions it would be: android_w.x.y.z
// For iOS only version it would be: ios_a.b.c.d
// After splitting into their respective components, the versions would be at the odd indices.
var versionAComponents = versionA.Split('_').ToList();
var versionBComponents = versionB.Split('_').ToList();
var androidComparison = MaxSdkUtils.VersionComparisonResult.Equal;
if (versionA.Contains("android") && versionB.Contains("android"))
{
var androidVersionA = versionAComponents[1];
var androidVersionB = versionBComponents[1];
androidComparison = MaxSdkUtils.CompareVersions(androidVersionA, androidVersionB);
// Remove the Android version component so that iOS versions can be processed.
versionAComponents.RemoveRange(0, 2);
versionBComponents.RemoveRange(0, 2);
}
else if (versionA.Contains("android"))
{
androidComparison = MaxSdkUtils.VersionComparisonResult.Greater;
// Remove the Android version component so that iOS versions can be processed.
versionAComponents.RemoveRange(0, 2);
}
else if (versionB.Contains("android"))
{
androidComparison = MaxSdkUtils.VersionComparisonResult.Lesser;
// Remove the Android version component so that iOS version can be processed.
versionBComponents.RemoveRange(0, 2);
}
var iosComparison = MaxSdkUtils.VersionComparisonResult.Equal;
if (versionA.Contains("ios") && versionB.Contains("ios"))
{
var iosVersionA = versionAComponents[1];
var iosVersionB = versionBComponents[1];
iosComparison = MaxSdkUtils.CompareVersions(iosVersionA, iosVersionB);
}
else if (versionA.Contains("ios"))
{
iosComparison = MaxSdkUtils.VersionComparisonResult.Greater;
}
else if (versionB.Contains("ios"))
{
iosComparison = MaxSdkUtils.VersionComparisonResult.Lesser;
}
// If either one of the Android or iOS version is greater, the entire version should be greater.
return (androidComparison == MaxSdkUtils.VersionComparisonResult.Greater || iosComparison == MaxSdkUtils.VersionComparisonResult.Greater) ? MaxSdkUtils.VersionComparisonResult.Greater : MaxSdkUtils.VersionComparisonResult.Lesser;
}
}
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 96efb4dba39eb48d2a60afab69786e6a
labels:
- al_max
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinIntegrationManagerUtils.cs
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -8,65 +8,68 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using VersionComparisonResult = MaxSdkUtils.VersionComparisonResult;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
public class AppLovinIntegrationManagerWindow : EditorWindow
{
private const string windowTitle = "AppLovin Integration Manager";
private const string WindowTitle = "AppLovin Integration Manager";
private const string appLovinSdkKeyLink = "https://dash.applovin.com/o/account#keys";
private const string AppLovinSdkKeyLink = "https://dash.applovin.com/o/account#keys";
private const string userTrackingUsageDescriptionDocsLink = "https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription";
private const string documentationTermsAndPrivacyPolicyFlow = "https://developers.applovin.com/en/unity/overview/terms-and-privacy-policy-flow";
private const string documentationAdaptersLink = "https://developers.applovin.com/en/unity/preparing-mediated-networks";
private const string documentationNote = "Please ensure that integration instructions (e.g. permissions, ATS settings, etc) specific to each network are implemented as well. Click the link below for more info:";
private const string uninstallIconExportPath = "MaxSdk/Resources/Images/uninstall_icon.png";
private const string alertIconExportPath = "MaxSdk/Resources/Images/alert_icon.png";
private const string warningIconExportPath = "MaxSdk/Resources/Images/warning_icon.png";
private const string UserTrackingUsageDescriptionDocsLink = "https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription";
private const string DocumentationTermsAndPrivacyPolicyFlow = "https://support.axon.ai/en/max/unity/overview/terms-and-privacy-policy-flow";
private const string DocumentationAdaptersLink = "https://support.axon.ai/en/max/unity/preparing-mediated-networks";
private const string DocumentationNote = "Please ensure that integration instructions (e.g. permissions, ATS settings, etc) specific to each network are implemented as well. Click the link below for more info:";
private const string UninstallIconExportPath = "MaxSdk/Resources/Images/uninstall_icon.png";
private const string InfoIconExportPath = "MaxSdk/Resources/Images/info_icon.png";
private const string WarningIconExportPath = "MaxSdk/Resources/Images/warning_icon.png";
private const string ErrorIconExportPath = "MaxSdk/Resources/Images/error_icon.png";
private const string qualityServiceRequiresGradleBuildErrorMsg = "AppLovin Quality Service integration via AppLovin Integration Manager requires Custom Gradle Template enabled or Unity 2018.2 or higher.\n" +
private const string QualityServiceRequiresGradleBuildErrorMsg = "AppLovin Quality Service integration via AppLovin Integration Manager requires Custom Gradle Template enabled or Unity 2018.2 or higher.\n" +
"If you would like to continue using your existing setup, please add Quality Service Plugin to your build.gradle manually.";
private const string customGradleVersionTooltip = "To set the version to 6.9.3, set the field to: https://services.gradle.org/distributions/gradle-6.9.3-bin.zip";
private const string customGradleToolsVersionTooltip = "To set the version to 4.2.0, set the field to: 4.2.0";
private const string CustomGradleVersionTooltip = "To set the version to 6.9.3, set the field to: https://services.gradle.org/distributions/gradle-6.9.3-bin.zip";
private const string CustomGradleToolsVersionTooltip = "To set the version to 4.2.0, set the field to: 4.2.0";
private const string keyShowMicroSdkPartners = "com.applovin.show_micro_sdk_partners";
private const string keyShowMediatedNetworks = "com.applovin.show_mediated_networks";
private const string keyShowSdkSettings = "com.applovin.show_sdk_settings";
private const string keyShowPrivacySettings = "com.applovin.show_privacy_settings";
private const string keyShowOtherSettings = "com.applovin.show_other_settings";
private const string KeyShowAlerts = "com.applovin.show_alerts";
private const string KeyShowMicroSdkPartners = "com.applovin.show_micro_sdk_partners";
private const string KeyShowMediatedNetworks = "com.applovin.show_mediated_networks";
private const string KeyShowSdkSettings = "com.applovin.show_sdk_settings";
private const string KeyShowPrivacySettings = "com.applovin.show_privacy_settings";
private const string KeyShowOtherSettings = "com.applovin.show_other_settings";
private const string expandButtonText = "+";
private const string collapseButtonText = "-";
private const string ExpandButtonText = "+";
private const string CollapseButtonText = "-";
private const string ExternalDependencyManagerPath = "Assets/ExternalDependencyManager";
private readonly string[] termsFlowPlatforms = new string[3] {"Both", "Android", "iOS"};
private readonly string[] debugUserGeographies = new string[2] {"Not Set", "GDPR"};
private Vector2 scrollPosition;
private static readonly Vector2 windowMinSize = new Vector2(750, 750);
private const float actionFieldWidth = 60f;
private const float upgradeAllButtonWidth = 80f;
private const float networkFieldMinWidth = 100f;
private const float versionFieldMinWidth = 190f;
private const float privacySettingLabelWidth = 250f;
private const float networkFieldWidthPercentage = 0.22f;
private const float versionFieldWidthPercentage = 0.36f; // There are two version fields. Each take 40% of the width, network field takes the remaining 20%.
private static float previousWindowWidth = windowMinSize.x;
private static GUILayoutOption networkWidthOption = GUILayout.Width(networkFieldMinWidth);
private static GUILayoutOption versionWidthOption = GUILayout.Width(versionFieldMinWidth);
private static readonly Vector2 WindowMinSize = new Vector2(750, 750);
private const float ActionFieldWidth = 70f;
private const float UpgradeAllButtonWidth = 80f;
private const float NetworkFieldMinWidth = 100f;
private const float VersionFieldMinWidth = 190f;
private const float PrivacySettingLabelWidth = 250f;
private const float NetworkFieldWidthPercentage = 0.22f;
private const float VersionFieldWidthPercentage = 0.36f; // There are two version fields. Each take 40% of the width, network field takes the remaining 20%.
private static float previousWindowWidth = WindowMinSize.x;
private static GUILayoutOption networkWidthOption = GUILayout.Width(NetworkFieldMinWidth);
private static GUILayoutOption versionWidthOption = GUILayout.Width(VersionFieldMinWidth);
private static GUILayoutOption privacySettingFieldWidthOption = GUILayout.Width(400);
private static readonly GUILayoutOption fieldWidth = GUILayout.Width(actionFieldWidth);
private static readonly GUILayoutOption upgradeAllButtonFieldWidth = GUILayout.Width(upgradeAllButtonWidth);
private static readonly GUILayoutOption collapseButtonWidthOption = GUILayout.Width(20f);
private static readonly GUILayoutOption FieldWidth = GUILayout.Width(ActionFieldWidth);
private static readonly GUILayoutOption UpgradeAllButtonFieldWidth = GUILayout.Width(UpgradeAllButtonWidth);
private static readonly GUILayoutOption CollapseButtonWidthOption = GUILayout.Width(20f);
private static readonly Color darkModeTextColor = new Color(0.29f, 0.6f, 0.8f);
private static readonly Color DarkModeTextColor = new Color(0.29f, 0.6f, 0.8f);
private GUIStyle titleLabelStyle;
private GUIStyle headerLabelStyle;
@@ -77,19 +80,19 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private PluginData pluginData;
private bool pluginDataLoadFailed;
private bool isPluginMoved;
private bool shouldShowGoogleWarning;
private bool networkButtonsEnabled = true;
private AppLovinEditorCoroutine loadDataCoroutine;
private Texture2D uninstallIcon;
private Texture2D alertIcon;
private Texture2D infoIcon;
private Texture2D warningIcon;
private Texture2D errorIcon;
public static void ShowManager()
{
var manager = GetWindow<AppLovinIntegrationManagerWindow>(utility: true, title: windowTitle, focus: true);
manager.minSize = windowMinSize;
var manager = GetWindow<AppLovinIntegrationManagerWindow>(utility: true, title: WindowTitle, focus: true);
manager.minSize = WindowMinSize;
}
#region Editor Window Lifecyle Methods
@@ -118,7 +121,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
linkLabelStyle = new GUIStyle(EditorStyles.label)
{
wordWrap = true,
normal = {textColor = EditorGUIUtility.isProSkin ? darkModeTextColor : Color.blue}
normal = {textColor = EditorGUIUtility.isProSkin ? DarkModeTextColor : Color.blue}
};
wrapTextLabelStyle = new GUIStyle(EditorStyles.label)
@@ -134,22 +137,27 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
};
// Load uninstall icon texture.
var uninstallIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(uninstallIconExportPath));
uninstallIcon = new Texture2D(0, 0, TextureFormat.RGBA32, false); // 1. Initial size doesn't matter here, will be automatically resized once the image asset is loaded. 2. Set mipChain to false, else the texture has a weird blurry effect.
var uninstallIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(UninstallIconExportPath));
// 1. Set the initial size to 1, as Unity 6000 no longer supports a width or height of 0.
// 2. The image will be automatically resized once the image asset is loaded.
// 3. Set mipChain to false, else the texture has a weird blurry effect.
uninstallIcon = new Texture2D(1, 1, TextureFormat.RGBA32, false);
uninstallIcon.LoadImage(uninstallIconData);
// Load alert icon texture.
var alertIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(alertIconExportPath));
alertIcon = new Texture2D(0, 0, TextureFormat.RGBA32, false);
alertIcon.LoadImage(alertIconData);
// Load info icon texture.
var infoIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(InfoIconExportPath));
infoIcon = new Texture2D(1, 1, TextureFormat.RGBA32, false);
infoIcon.LoadImage(infoIconData);
// Load warning icon texture.
var warningIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(warningIconExportPath));
warningIcon = new Texture2D(0, 0, TextureFormat.RGBA32, false);
var warningIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(WarningIconExportPath));
warningIcon = new Texture2D(1, 1, TextureFormat.RGBA32, false);
warningIcon.LoadImage(warningIconData);
var pluginPath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, "MaxSdk");
isPluginMoved = !AppLovinIntegrationManager.DefaultPluginExportPath.Equals(pluginPath);
// Load error icon texture.
var errorIconData = File.ReadAllBytes(MaxSdkUtils.GetAssetPathForExportPath(ErrorIconExportPath));
errorIcon = new Texture2D(1, 1, TextureFormat.RGBA32, false);
errorIcon.LoadImage(errorIconData);
}
private void OnEnable()
@@ -174,17 +182,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private void OnWindowEnabled()
{
AppLovinIntegrationManager.downloadPluginProgressCallback = OnDownloadPluginProgress;
// Plugin downloaded and imported. Update current versions for the imported package.
AppLovinIntegrationManager.importPackageCompletedCallback = OnImportPackageCompleted;
// Disable old consent flow if new flow is enabled.
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled)
{
AppLovinSettings.Instance.ConsentFlowEnabled = false;
AppLovinSettings.Instance.SaveAsync();
}
AppLovinIntegrationManager.OnImportPackageStartedCallback = OnImportPackageStarted;
AppLovinIntegrationManager.OnImportPackageCompletedCallback = OnImportPackageCompleted;
Load();
}
@@ -221,17 +221,36 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
// Draw AppLovin MAX plugin details
EditorGUILayout.LabelField("AppLovin MAX Plugin Details", titleLabelStyle);
DrawPluginDetails();
if (pluginData != null && pluginData.PartnerMicroSdks != null)
// Draw alerts
if (pluginData != null && pluginData.Alerts != null)
{
DrawCollapsableSection(keyShowMicroSdkPartners, "AppLovin Micro SDK Partners", DrawPartnerMicroSdks);
var alertsToShow = pluginData.Alerts.Where(alert => alert.ShouldShowAlert()).ToList();
if (alertsToShow.Count > 0)
{
EditorGUILayout.BeginHorizontal();
var showAlertDetails = DrawExpandCollapseButton(KeyShowAlerts);
EditorGUILayout.LabelField("Alerts", titleLabelStyle, GUILayout.Width(45));
DrawAlertCount(alertsToShow);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
if (showAlertDetails)
{
DrawAlerts(alertsToShow);
}
}
}
// Draw Micro SDK Partners
if (pluginData != null && !MaxSdkUtils.IsNullOrEmpty(pluginData.PartnerMicroSdks))
{
DrawCollapsibleSection(KeyShowMicroSdkPartners, "AppLovin Micro SDK Partners", DrawPartnerMicroSdks);
}
// Draw mediated networks);
EditorGUILayout.BeginHorizontal();
var showDetails = DrawExpandCollapseButton(keyShowMediatedNetworks);
var showDetails = DrawExpandCollapseButton(KeyShowMediatedNetworks);
EditorGUILayout.LabelField("Mediated Networks", titleLabelStyle);
GUILayout.FlexibleSpace();
DrawUpgradeAllButton();
@@ -241,22 +260,28 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
DrawMediatedNetworks();
}
if (!AppLovinIntegrationManager.IsPluginInPackageManager)
{
EditorGUILayout.LabelField("Unity Package Manager Migration", titleLabelStyle);
DrawPluginMigrationHelper();
}
// Draw AppLovin Quality Service settings
DrawCollapsableSection(keyShowSdkSettings, "SDK Settings", DrawQualityServiceSettings);
DrawCollapsibleSection(KeyShowSdkSettings, "SDK Settings", DrawQualityServiceSettings);
DrawCollapsableSection(keyShowPrivacySettings, "Privacy Settings", DrawPrivacySettings);
DrawCollapsibleSection(KeyShowPrivacySettings, "Privacy Settings", DrawPrivacySettings);
DrawCollapsableSection(keyShowOtherSettings, "Other Settings", DrawOtherSettings);
DrawCollapsibleSection(KeyShowOtherSettings, "Other Settings", DrawOtherSettings);
// Draw Unity environment details
EditorGUILayout.LabelField("Unity Environment Details", titleLabelStyle);
DrawUnityEnvironmentDetails();
// Draw documentation notes
EditorGUILayout.LabelField(new GUIContent(documentationNote), wrapTextLabelStyle);
if (GUILayout.Button(new GUIContent(documentationAdaptersLink), linkLabelStyle))
EditorGUILayout.LabelField(new GUIContent(DocumentationNote), wrapTextLabelStyle);
if (GUILayout.Button(new GUIContent(DocumentationAdaptersLink), linkLabelStyle))
{
Application.OpenURL(documentationAdaptersLink);
Application.OpenURL(DocumentationAdaptersLink);
}
}
@@ -285,7 +310,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.BeginHorizontal();
GUILayout.Space(5);
EditorGUILayout.LabelField("Failed to load plugin data. Please click retry or restart the integration manager.", titleLabelStyle);
if (GUILayout.Button("Retry", fieldWidth))
if (GUILayout.Button("Retry", FieldWidth))
{
pluginDataLoadFailed = false;
Load();
@@ -331,7 +356,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
var appLovinMax = pluginData.AppLovinMax;
// Check if a newer version is available to enable the upgrade button.
var upgradeButtonEnabled = appLovinMax.CurrentToLatestVersionComparisonResult == VersionComparisonResult.Lesser;
var upgradeButtonEnabled = appLovinMax.CurrentToLatestVersionComparisonResult == MaxSdkUtils.VersionComparisonResult.Lesser;
DrawPluginDetailRow("Unity 3D", appLovinMax.CurrentVersions.Unity, appLovinMax.LatestVersions.Unity);
DrawPluginDetailRow("Android", appLovinMax.CurrentVersions.Android, appLovinMax.LatestVersions.Android);
DrawPluginDetailRow("iOS", appLovinMax.CurrentVersions.Ios, appLovinMax.LatestVersions.Ios);
@@ -340,10 +365,15 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUI.enabled = upgradeButtonEnabled;
if (GUILayout.Button(new GUIContent("Upgrade"), fieldWidth))
var action = appLovinMax.IsCurrentlyInstalling ? "Installing..." : "Upgrade";
GUI.enabled = upgradeButtonEnabled && !appLovinMax.IsCurrentlyInstalling;
if (GUILayout.Button(new GUIContent(action), FieldWidth))
{
AppLovinEditorCoroutine.StartCoroutine(AppLovinIntegrationManager.Instance.DownloadPlugin(appLovinMax));
// Only show "Installing..." if the plugin is in the Assets folder
// Manifest edits don't trigger import callbacks, and UPM resolution locks the UI anyway.
appLovinMax.IsCurrentlyInstalling = !AppLovinIntegrationManager.IsPluginInPackageManager;
AppLovinEditorCoroutine.StartCoroutine(AppLovinPackageManager.AddNetwork(pluginData.AppLovinMax, true));
}
GUI.enabled = true;
@@ -358,6 +388,84 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.EndHorizontal();
}
/// <summary>
/// Draw the number of each alert type next to the alert section header.
/// </summary>
private void DrawAlertCount(List<Alert> alerts)
{
if (pluginData == null) return;
var infoAlertsCount = alerts.Count(alert => alert.Severity == Severity.Info);
var warningAlertsCount = alerts.Count(alert => alert.Severity == Severity.Warning);
var errorAlertsCount = alerts.Count(alert => alert.Severity == Severity.Error);
GUILayout.Label(infoIcon, GUILayout.Width(20), GUILayout.Height(20));
EditorGUILayout.LabelField(AlertCountToString(infoAlertsCount), GUILayout.Width(20));
GUILayout.Label(warningIcon, GUILayout.Width(20), GUILayout.Height(20));
EditorGUILayout.LabelField(AlertCountToString(warningAlertsCount), GUILayout.Width(20));
GUILayout.Label(errorIcon, GUILayout.Width(20), GUILayout.Height(20));
EditorGUILayout.LabelField(AlertCountToString(errorAlertsCount), GUILayout.Width(20));
}
/// <summary>
/// Draw the list of alerts grouped by severity.
/// </summary>
private void DrawAlerts(List<Alert> alerts)
{
GUILayout.BeginHorizontal();
GUILayout.Space(10);
using (new EditorGUILayout.VerticalScope("box"))
{
DrawAlertsOfType(alerts, Severity.Error);
DrawAlertsOfType(alerts, Severity.Warning);
DrawAlertsOfType(alerts, Severity.Info);
}
GUILayout.Space(5);
GUILayout.EndHorizontal();
}
private void DrawAlertsOfType(List<Alert> alerts, Severity severity)
{
var alertsOfType = alerts.Where(alert => alert.Severity == severity).ToList();
foreach (var alert in alertsOfType)
{
DrawAlert(alert);
}
}
/// <summary>
/// Draw a single alert.
/// </summary>
private void DrawAlert(Alert alert)
{
using (new EditorGUILayout.HorizontalScope())
{
using (new EditorGUILayout.VerticalScope(GUILayout.Width(20)))
{
GUILayout.Space(2);
GUILayout.Label(GetSeverityIcon(alert.Severity), GUILayout.Width(20), GUILayout.Height(20));
}
using (new EditorGUILayout.VerticalScope())
{
GUILayout.Label(alert.Title, headerLabelStyle);
EditorGUILayout.LabelField(alert.Message, wrapTextLabelStyle);
if (MaxSdkUtils.IsValidString(alert.Url))
{
if (GUILayout.Button(new GUIContent(alert.Url), linkLabelStyle))
{
Application.OpenURL(alert.Url);
}
}
GUILayout.Space(2);
}
}
GUILayout.Space(10);
}
/// <summary>
/// Draws the headers for a table.
/// </summary>
@@ -374,7 +482,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (drawAction)
{
GUILayout.FlexibleSpace();
GUILayout.Button("Actions", headerLabelStyle, fieldWidth);
GUILayout.Button("Actions", headerLabelStyle, FieldWidth);
GUILayout.Space(5);
}
}
@@ -478,13 +586,13 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var comparison = network.CurrentToLatestVersionComparisonResult;
// A newer version is available
if (comparison == VersionComparisonResult.Lesser)
if (comparison == MaxSdkUtils.VersionComparisonResult.Lesser)
{
action = "Upgrade";
isActionEnabled = true;
}
// Current installed version is newer than latest version from DB (beta version)
else if (comparison == VersionComparisonResult.Greater)
else if (comparison == MaxSdkUtils.VersionComparisonResult.Greater)
{
action = "Installed";
isActionEnabled = false;
@@ -510,18 +618,25 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (network.RequiresUpdate)
{
GUILayout.Label(new GUIContent {image = alertIcon, tooltip = "Adapter not compatible, please update to the latest version."}, iconStyle);
GUILayout.Label(new GUIContent {image = errorIcon, tooltip = "Adapter not compatible, please update to the latest version."}, iconStyle);
}
else if ((network.Name.Equals("ADMOB_NETWORK") || network.Name.Equals("GOOGLE_AD_MANAGER_NETWORK")) && shouldShowGoogleWarning)
{
GUILayout.Label(new GUIContent {image = warningIcon, tooltip = "You may see unexpected errors if you use different versions of the AdMob and Google Ad Manager adapter SDKs."}, iconStyle);
}
GUI.enabled = networkButtonsEnabled && isActionEnabled;
if (GUILayout.Button(new GUIContent(action), fieldWidth))
if (network.IsCurrentlyInstalling)
{
// Download the plugin.
AppLovinEditorCoroutine.StartCoroutine(AppLovinIntegrationManager.Instance.DownloadPlugin(network));
action = "Installing...";
}
GUI.enabled = networkButtonsEnabled && isActionEnabled && !network.IsCurrentlyInstalling;
if (GUILayout.Button(new GUIContent(action), FieldWidth))
{
// Only show "Installing..." if the plugin is in the Assets folder
// Manifest edits don't trigger import callbacks, and UPM resolution locks the UI anyway.
network.IsCurrentlyInstalling = !AppLovinIntegrationManager.IsPluginInPackageManager;
AppLovinEditorCoroutine.StartCoroutine(AppLovinPackageManager.AddNetwork(network, true));
}
GUI.enabled = true;
@@ -530,20 +645,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUI.enabled = networkButtonsEnabled && isInstalled;
if (GUILayout.Button(new GUIContent {image = uninstallIcon, tooltip = "Uninstall"}, iconStyle))
{
EditorUtility.DisplayProgressBar("Integration Manager", "Deleting " + network.Name + "...", 0.5f);
var pluginRoot = AppLovinIntegrationManager.MediationSpecificPluginParentDirectory;
foreach (var pluginFilePath in network.PluginFilePaths)
{
var filePath = Path.Combine(pluginRoot, pluginFilePath);
FileUtil.DeleteFileOrDirectory(filePath);
FileUtil.DeleteFileOrDirectory(filePath + ".meta");
}
AppLovinIntegrationManager.UpdateCurrentVersions(network, pluginRoot);
// Refresh UI
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
AppLovinPackageManager.RemoveNetwork(network);
AppLovinPackageManager.UpdateCurrentVersions(network);
UpdateShouldShowGoogleWarningIfNeeded();
}
GUI.enabled = true;
@@ -562,11 +666,10 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (network.Name.Equals("ADMOB_NETWORK"))
{
// Show only one set of text boxes if both ADMOB and GAM are installed
if (AppLovinIntegrationManager.IsAdapterInstalled("GoogleAdManager")) return;
if (AppLovinPackageManager.IsAdapterInstalled(pluginData, "GOOGLE_AD_MANAGER_NETWORK")) return;
DrawGoogleAppIdTextBox();
}
// Custom integration for GAM where the user can enter the Android and iOS App IDs.
else if (network.Name.Equals("GOOGLE_AD_MANAGER_NETWORK"))
{
@@ -597,7 +700,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private void DrawUpgradeAllButton()
{
GUI.enabled = NetworksRequireUpgrade();
if (GUILayout.Button(new GUIContent("Upgrade All"), upgradeAllButtonFieldWidth))
if (GUILayout.Button(new GUIContent("Upgrade All"), UpgradeAllButtonFieldWidth))
{
AppLovinEditorCoroutine.StartCoroutine(UpgradeAllNetworks());
}
@@ -606,6 +709,53 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.Space(10);
}
private void DrawPluginMigrationHelper()
{
GUILayout.BeginHorizontal();
GUILayout.Space(10);
using (new EditorGUILayout.VerticalScope("box"))
{
GUILayout.Space(5);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
EditorGUILayout.LabelField(new GUIContent("This will migrate the AppLovin MAX Unity Plugin and adapters to the Unity Package Manager."), wrapTextLabelStyle);
GUILayout.Space(4);
GUILayout.EndHorizontal();
GUI.enabled = true;
GUILayout.Space(3);
GUILayout.FlexibleSpace();
GUILayout.BeginHorizontal();
GUILayout.Space(10);
var migrationText = "Upgrade All Adapters and Migrate to UPM";
if (GUILayout.Button(new GUIContent(migrationText)))
{
if (EditorUtility.DisplayDialog("Migrate to UPM?",
"Are you sure you want to migrate the SDK and adapters to UPM? This action will move both the MAX SDK and its adapters.", "Yes", "No"))
{
var deleteExternalDependencyManager = false;
if (Directory.Exists(ExternalDependencyManagerPath))
{
deleteExternalDependencyManager = EditorUtility.DisplayDialog("External Dependency Manager Detected",
"Our plugin includes the External Dependency Manager via the Unity Package Manager. Would you like us to automatically remove the existing External Dependency Manager folder, or would you prefer to manage it manually?", "Remove Automatically", "Manage Manually");
}
AppLovinPluginMigrationHelper.MigrateToUnityPackageManager(pluginData, deleteExternalDependencyManager);
}
}
GUILayout.Space(10);
GUILayout.EndHorizontal();
GUILayout.Space(5);
EditorGUILayout.HelpBox("Ensure all changes are committed before migration.", MessageType.Warning);
}
GUILayout.Space(5);
GUILayout.EndHorizontal();
}
private void DrawQualityServiceSettings()
{
GUILayout.BeginHorizontal();
@@ -618,20 +768,20 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
EditorGUILayout.HelpBox(qualityServiceRequiresGradleBuildErrorMsg, MessageType.Warning);
EditorGUILayout.HelpBox(QualityServiceRequiresGradleBuildErrorMsg, MessageType.Warning);
GUILayout.Space(4);
GUILayout.EndHorizontal();
GUILayout.Space(4);
}
AppLovinSettings.Instance.SdkKey = DrawTextField("AppLovin SDK Key", AppLovinSettings.Instance.SdkKey, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.SdkKey = DrawTextField("AppLovin SDK Key", AppLovinSettings.Instance.SdkKey, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
GUILayout.Button("You can find your SDK key here: ", wrapTextLabelStyle, GUILayout.Width(185)); // Setting a fixed width since Unity adds arbitrary padding at the end leaving a space between link and text.
if (GUILayout.Button(new GUIContent(appLovinSdkKeyLink), linkLabelStyle))
if (GUILayout.Button(new GUIContent(AppLovinSdkKeyLink), linkLabelStyle))
{
Application.OpenURL(appLovinSdkKeyLink);
Application.OpenURL(AppLovinSdkKeyLink);
}
GUILayout.FlexibleSpace();
@@ -686,107 +836,14 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.BeginHorizontal();
GUILayout.Space(10);
using (new EditorGUILayout.VerticalScope("box"))
{
if (AppLovinSettings.Instance.ConsentFlowEnabled)
{
DrawTermsFlowSettings();
}
else
{
DrawConsentFlowSettings();
}
}
GUILayout.Space(5);
GUILayout.EndHorizontal();
}
private void DrawTermsFlowSettings()
{
GUILayout.BeginHorizontal();
GUILayout.Space(4);
EditorGUILayout.HelpBox("The Terms Flow has been deprecated; switch to the MAX Terms and Privacy Policy Flow instead.", MessageType.Warning); // TODO Refine
GUILayout.Space(4);
GUILayout.EndHorizontal();
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Switch to MAX Terms and Privacy Policy Flow"))
{
AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl = AppLovinSettings.Instance.ConsentFlowPrivacyPolicyUrl;
AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl = AppLovinSettings.Instance.ConsentFlowTermsOfServiceUrl;
AppLovinInternalSettings.Instance.ConsentFlowEnabled = true;
AppLovinSettings.Instance.ConsentFlowEnabled = false;
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
AppLovinSettings.Instance.ConsentFlowEnabled = GUILayout.Toggle(AppLovinSettings.Instance.ConsentFlowEnabled, " Enable Terms Flow");
GUILayout.FlexibleSpace();
GUI.enabled = AppLovinSettings.Instance.ConsentFlowEnabled;
AppLovinSettings.Instance.ConsentFlowPlatform = (Platform) EditorGUILayout.Popup((int) AppLovinSettings.Instance.ConsentFlowPlatform, termsFlowPlatforms);
GUILayout.EndHorizontal();
GUILayout.Space(4);
AppLovinSettings.Instance.ConsentFlowPrivacyPolicyUrl = DrawTextField("Privacy Policy URL", AppLovinSettings.Instance.ConsentFlowPrivacyPolicyUrl, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.ConsentFlowTermsOfServiceUrl = DrawTextField("Terms of Service URL (optional)", AppLovinSettings.Instance.ConsentFlowTermsOfServiceUrl, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.UserTrackingUsageDescriptionEn = DrawTextField("User Tracking Usage Description (iOS only)", AppLovinSettings.Instance.UserTrackingUsageDescriptionEn, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
AppLovinSettings.Instance.UserTrackingUsageLocalizationEnabled = GUILayout.Toggle(AppLovinSettings.Instance.UserTrackingUsageLocalizationEnabled, " Localize User Tracking Usage Description (iOS only)");
GUILayout.EndHorizontal();
GUILayout.Space(4);
if (AppLovinSettings.Instance.UserTrackingUsageLocalizationEnabled)
{
AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHans = DrawTextField("Chinese, Simplified (zh-Hans)", AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHans, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHant = DrawTextField("Chinese, Traditional (zh-Hant)", AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHant, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption); // TODO: Remove new mark for next release.
AppLovinSettings.Instance.UserTrackingUsageDescriptionFr = DrawTextField("French (fr)", AppLovinSettings.Instance.UserTrackingUsageDescriptionFr, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.UserTrackingUsageDescriptionDe = DrawTextField("German (de)", AppLovinSettings.Instance.UserTrackingUsageDescriptionDe, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.UserTrackingUsageDescriptionJa = DrawTextField("Japanese (ja)", AppLovinSettings.Instance.UserTrackingUsageDescriptionJa, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.UserTrackingUsageDescriptionKo = DrawTextField("Korean (ko)", AppLovinSettings.Instance.UserTrackingUsageDescriptionKo, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinSettings.Instance.UserTrackingUsageDescriptionEs = DrawTextField("Spanish (es)", AppLovinSettings.Instance.UserTrackingUsageDescriptionEs, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
EditorGUILayout.HelpBox("MAX may add more localized strings to this list in the future, which will set the default value of the User Tracking Usage Description string for more locales. If you are overriding these with your own custom translations, you may want to review this list whenever you upgrade the plugin to see if there are new entries you may want to customize.", MessageType.Info);
GUILayout.Space(4);
GUILayout.EndHorizontal();
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
EditorGUILayout.HelpBox("If you have your own implementation of InfoPlist.strings localization implementation, please use that instead. Using both at the same time may cause conflicts.", MessageType.Info);
GUILayout.Space(4);
GUILayout.EndHorizontal();
}
GUI.enabled = true;
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
GUILayout.Button("Click the link below for more information about User Tracking Usage Description: ", wrapTextLabelStyle);
GUILayout.Space(4);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Space(4);
if (GUILayout.Button(new GUIContent(userTrackingUsageDescriptionDocsLink), linkLabelStyle))
{
Application.OpenURL(userTrackingUsageDescriptionDocsLink);
}
GUILayout.Space(4);
GUILayout.EndHorizontal();
GUILayout.Space(4);
}
private void DrawConsentFlowSettings()
{
GUILayout.BeginHorizontal();
@@ -809,9 +866,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Space(4);
if (GUILayout.Button(new GUIContent(documentationTermsAndPrivacyPolicyFlow), linkLabelStyle))
if (GUILayout.Button(new GUIContent(DocumentationTermsAndPrivacyPolicyFlow), linkLabelStyle))
{
Application.OpenURL(documentationTermsAndPrivacyPolicyFlow);
Application.OpenURL(DocumentationTermsAndPrivacyPolicyFlow);
}
GUILayout.Space(4);
@@ -819,8 +876,14 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.Space(8);
AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl = DrawTextField("Privacy Policy URL", AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl = DrawTextField("Terms of Service URL (optional)", AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl = DrawTextField("Privacy Policy URL", AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption);
AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl = DrawTextField("Terms of Service URL (optional)", AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption);
GUILayout.Space(4);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR = GUILayout.Toggle(AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR, " Show Terms and Privacy Policy Flow when in GDPR Regions");
GUILayout.EndHorizontal();
GUILayout.Space(4);
GUILayout.BeginHorizontal();
@@ -837,7 +900,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.Space(4);
GUILayout.Space(4);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn = DrawTextField("User Tracking Usage Description", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn = DrawTextField("User Tracking Usage Description", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
GUILayout.BeginHorizontal();
GUILayout.Space(4);
@@ -847,13 +910,13 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (AppLovinInternalSettings.Instance.UserTrackingUsageLocalizationEnabled)
{
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans = DrawTextField("Chinese, Simplified (zh-Hans)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant = DrawTextField("Chinese, Traditional (zh-Hant)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr = DrawTextField("French (fr)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe = DrawTextField("German (de)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa = DrawTextField("Japanese (ja)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo = DrawTextField("Korean (ko)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs = DrawTextField("Spanish (es)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans = DrawTextField("Chinese, Simplified (zh-Hans)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant = DrawTextField("Chinese, Traditional (zh-Hant)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr = DrawTextField("French (fr)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe = DrawTextField("German (de)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa = DrawTextField("Japanese (ja)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo = DrawTextField("Korean (ko)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs = DrawTextField("Spanish (es)", AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, isEditableTextField);
GUILayout.Space(4);
GUILayout.BeginHorizontal();
@@ -871,9 +934,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Space(4);
if (GUILayout.Button(new GUIContent(userTrackingUsageDescriptionDocsLink), linkLabelStyle))
if (GUILayout.Button(new GUIContent(UserTrackingUsageDescriptionDocsLink), linkLabelStyle))
{
Application.OpenURL(userTrackingUsageDescriptionDocsLink);
Application.OpenURL(UserTrackingUsageDescriptionDocsLink);
}
GUILayout.Space(4);
@@ -901,10 +964,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
GUILayout.Space(10);
using (new EditorGUILayout.VerticalScope("box"))
{
GUILayout.Space(5);
AppLovinSettings.Instance.SetAttributionReportEndpoint = DrawOtherSettingsToggle(AppLovinSettings.Instance.SetAttributionReportEndpoint, " Set Advertising Attribution Report Endpoint in Info.plist (iOS only)");
GUILayout.Space(5);
AppLovinSettings.Instance.AddApsSkAdNetworkIds = DrawOtherSettingsToggle(AppLovinSettings.Instance.AddApsSkAdNetworkIds, " Add Amazon Publisher Services SKAdNetworkID's");
GUILayout.Space(5);
var autoUpdateEnabled = DrawOtherSettingsToggle(EditorPrefs.GetBool(AppLovinAutoUpdater.KeyAutoUpdateEnabled, true), " Enable Auto Update", "Checks for AppLovin MAX plugin updates and notifies you when an update is available.");
EditorPrefs.SetBool(AppLovinAutoUpdater.KeyAutoUpdateEnabled, autoUpdateEnabled);
@@ -912,8 +971,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var verboseLoggingEnabled = DrawOtherSettingsToggle(EditorPrefs.GetBool(MaxSdkLogger.KeyVerboseLoggingEnabled, false), " Enable Verbose Logging");
EditorPrefs.SetBool(MaxSdkLogger.KeyVerboseLoggingEnabled, verboseLoggingEnabled);
GUILayout.Space(5);
AppLovinSettings.Instance.CustomGradleVersionUrl = DrawTextField("Custom Gradle Version URL", AppLovinSettings.Instance.CustomGradleVersionUrl, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, tooltip: customGradleVersionTooltip);
AppLovinSettings.Instance.CustomGradleToolsVersion = DrawTextField("Custom Gradle Tools Version", AppLovinSettings.Instance.CustomGradleToolsVersion, GUILayout.Width(privacySettingLabelWidth), privacySettingFieldWidthOption, tooltip: customGradleToolsVersionTooltip);
AppLovinSettings.Instance.CustomGradleVersionUrl = DrawTextField("Custom Gradle Version URL", AppLovinSettings.Instance.CustomGradleVersionUrl, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, tooltip: CustomGradleVersionTooltip);
AppLovinSettings.Instance.CustomGradleToolsVersion = DrawTextField("Custom Gradle Tools Version", AppLovinSettings.Instance.CustomGradleToolsVersion, GUILayout.Width(PrivacySettingLabelWidth), privacySettingFieldWidthOption, tooltip: CustomGradleToolsVersionTooltip);
EditorGUILayout.HelpBox("This will overwrite the gradle build tools version in your base gradle template.", MessageType.Info);
}
@@ -965,7 +1024,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
}
private void DrawCollapsableSection(string keyShowDetails, string label, Action drawContent)
private void DrawCollapsibleSection(string keyShowDetails, string label, Action drawContent)
{
EditorGUILayout.BeginHorizontal();
var showDetails = DrawExpandCollapseButton(keyShowDetails);
@@ -982,8 +1041,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private bool DrawExpandCollapseButton(string keyShowDetails)
{
var showDetails = EditorPrefs.GetBool(keyShowDetails, true);
var buttonText = showDetails ? collapseButtonText : expandButtonText;
if (GUILayout.Button(buttonText, collapseButtonWidthOption))
var buttonText = showDetails ? CollapseButtonText : ExpandButtonText;
if (GUILayout.Button(buttonText, CollapseButtonWidthOption))
{
EditorPrefs.SetBool(keyShowDetails, !showDetails);
}
@@ -997,15 +1056,15 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private void CalculateFieldWidth()
{
var currentWidth = position.width;
var availableWidth = currentWidth - actionFieldWidth - 80; // NOTE: Magic number alert. This is the sum of all the spacing the fields and other UI elements.
var networkLabelWidth = Math.Max(networkFieldMinWidth, availableWidth * networkFieldWidthPercentage);
var availableWidth = currentWidth - ActionFieldWidth - 80; // NOTE: Magic number alert. This is the sum of all the spacing the fields and other UI elements.
var networkLabelWidth = Math.Max(NetworkFieldMinWidth, availableWidth * NetworkFieldWidthPercentage);
networkWidthOption = GUILayout.Width(networkLabelWidth);
var versionLabelWidth = Math.Max(versionFieldMinWidth, availableWidth * versionFieldWidthPercentage);
var versionLabelWidth = Math.Max(VersionFieldMinWidth, availableWidth * VersionFieldWidthPercentage);
versionWidthOption = GUILayout.Width(versionLabelWidth);
const int textFieldOtherUiElementsWidth = 55; // NOTE: Magic number alert. This is the sum of all the spacing the fields and other UI elements.
var availableUserDescriptionTextFieldWidth = currentWidth - privacySettingLabelWidth - textFieldOtherUiElementsWidth;
var availableUserDescriptionTextFieldWidth = currentWidth - PrivacySettingLabelWidth - textFieldOtherUiElementsWidth;
privacySettingFieldWidthOption = GUILayout.Width(availableUserDescriptionTextFieldWidth);
}
@@ -1037,31 +1096,14 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}));
}
/// <summary>
/// Callback method that will be called with progress updates when the plugin is being downloaded.
/// </summary>
public static void OnDownloadPluginProgress(string pluginName, float progress, bool done)
private void OnImportPackageStarted(Network network)
{
// Download is complete. Clear progress bar.
if (done)
{
EditorUtility.ClearProgressBar();
}
// Download is in progress, update progress bar.
else
{
if (EditorUtility.DisplayCancelableProgressBar(windowTitle, string.Format("Downloading {0} plugin...", pluginName), progress))
{
AppLovinIntegrationManager.Instance.CancelDownload();
EditorUtility.ClearProgressBar();
}
}
network.IsCurrentlyInstalling = false;
}
private void OnImportPackageCompleted(Network network)
{
var parentDirectory = network.Name.Equals("APPLOVIN_NETWORK") ? AppLovinIntegrationManager.PluginParentDirectory : AppLovinIntegrationManager.MediationSpecificPluginParentDirectory;
AppLovinIntegrationManager.UpdateCurrentVersions(network, parentDirectory);
AppLovinPackageManager.UpdateCurrentVersions(network);
UpdateShouldShowGoogleWarningIfNeeded();
}
@@ -1079,7 +1121,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var googleAdManagerNetwork = networks.FirstOrDefault(foundNetwork => foundNetwork.Name.Equals("GOOGLE_AD_MANAGER_NETWORK"));
if (googleNetwork != null && googleAdManagerNetwork != null &&
!string.IsNullOrEmpty(googleNetwork.CurrentVersions.Unity) && !string.IsNullOrEmpty(googleAdManagerNetwork.CurrentVersions.Unity) &&
MaxSdkUtils.IsValidString(googleNetwork.CurrentVersions.Unity) && MaxSdkUtils.IsValidString(googleAdManagerNetwork.CurrentVersions.Unity) &&
!googleNetwork.CurrentVersions.HasEqualSdkVersions(googleAdManagerNetwork.CurrentVersions))
{
shouldShowGoogleWarning = true;
@@ -1102,9 +1144,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
var comparison = network.CurrentToLatestVersionComparisonResult;
// A newer version is available
if (!string.IsNullOrEmpty(network.CurrentVersions.Unity) && comparison == VersionComparisonResult.Lesser)
if (MaxSdkUtils.IsValidString(network.CurrentVersions.Unity) && comparison == MaxSdkUtils.VersionComparisonResult.Lesser)
{
yield return AppLovinIntegrationManager.Instance.DownloadPlugin(network, false);
yield return AppLovinPackageManager.AddNetwork(network, false);
}
}
@@ -1123,7 +1165,33 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
if (pluginData == null || pluginData.AppLovinMax.CurrentVersions == null) return false;
var networks = pluginData.MediatedNetworks;
return networks.Any(network => !string.IsNullOrEmpty(network.CurrentVersions.Unity) && network.CurrentToLatestVersionComparisonResult == VersionComparisonResult.Lesser);
return networks.Any(network => MaxSdkUtils.IsValidString(network.CurrentVersions.Unity) && network.CurrentToLatestVersionComparisonResult == MaxSdkUtils.VersionComparisonResult.Lesser);
}
/// <summary>
/// Takes in an int representing the count of an alert and returns it as a string or "9+" if greater than 9.
/// </summary>
private string AlertCountToString(int count)
{
return count > 9 ? "9+" : count.ToString();
}
/// <summary>
/// Returns the icon for the given severity type.
/// </summary>
private Texture2D GetSeverityIcon(Severity severity)
{
switch (severity)
{
case Severity.Info:
return infoIcon;
case Severity.Warning:
return warningIcon;
case Severity.Error:
return errorIcon;
default:
return infoIcon;
}
}
#endregion
@@ -1,5 +1,5 @@
//
// AppLovinInternalSettigns.cs
// AppLovinInternalSettings.cs
// AppLovin User Engagement Unity Plugin
//
// Created by Santosh Bagadi on 9/15/22.
@@ -22,18 +22,19 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
private static AppLovinInternalSettings instance;
public const string DefaultUserTrackingDescriptionEn = "This uses device info for more personalized ads and content";
public const string DefaultUserTrackingDescriptionDe = "Dies benutzt Gerätinformationen für relevantere Werbeinhalte";
public const string DefaultUserTrackingDescriptionEs = "Esto utiliza la información del dispositivo para anuncios y contenido más personalizados";
public const string DefaultUserTrackingDescriptionFr = "Cela permet d'utiliser les informations du téléphone pour afficher des contenus publicitaires plus pertinents.";
public const string DefaultUserTrackingDescriptionJa = "これはユーザーデータをもとに、より関連性の高い広告コンテンツをお客様に提供します";
public const string DefaultUserTrackingDescriptionKo = "보다 개인화된 광고 및 콘텐츠를 위해 기기 정보를 사용합니다.";
public const string DefaultUserTrackingDescriptionZhHans = "我们使用设备信息来提供个性化的广告和内容。";
public const string DefaultUserTrackingDescriptionZhHant = "我們使用設備信息來提供個性化的廣告和內容。";
private const string DefaultUserTrackingDescriptionEn = "This uses device info for more personalized ads and content";
private const string DefaultUserTrackingDescriptionDe = "Dies benutzt Gerätinformationen für relevantere Werbeinhalte";
private const string DefaultUserTrackingDescriptionEs = "Esto utiliza la información del dispositivo para anuncios y contenido más personalizados";
private const string DefaultUserTrackingDescriptionFr = "Cela permet d'utiliser les informations du téléphone pour afficher des contenus publicitaires plus pertinents.";
private const string DefaultUserTrackingDescriptionJa = "これはユーザーデータをもとに、より関連性の高い広告コンテンツをお客様に提供します";
private const string DefaultUserTrackingDescriptionKo = "보다 개인화된 광고 및 콘텐츠를 위해 기기 정보를 사용합니다.";
private const string DefaultUserTrackingDescriptionZhHans = "我们使用设备信息来提供个性化的广告和内容。";
private const string DefaultUserTrackingDescriptionZhHant = "我們使用設備信息來提供個性化的廣告和內容。";
[SerializeField] private bool consentFlowEnabled;
[SerializeField] private string consentFlowPrivacyPolicyUrl = string.Empty;
[SerializeField] private string consentFlowTermsOfServiceUrl = string.Empty;
[SerializeField] private bool shouldShowTermsAndPrivacyPolicyAlertInGDPR;
[SerializeField] private bool overrideDefaultUserTrackingUsageDescriptions;
[SerializeField] private MaxSdkBase.ConsentFlowUserGeography debugUserGeography;
[SerializeField] private string userTrackingUsageDescriptionEn = string.Empty;
@@ -140,9 +141,18 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
set { consentFlowTermsOfServiceUrl = value; }
}
/// <summary>
/// Whether or not to show the Terms and Privacy Policy alert in GDPR regions prior to presenting the CMP prompt.
/// </summary>
public bool ShouldShowTermsAndPrivacyPolicyAlertInGDPR
{
get { return shouldShowTermsAndPrivacyPolicyAlertInGDPR; }
set { shouldShowTermsAndPrivacyPolicyAlertInGDPR = value; }
}
/// <summary>
/// A User Tracking Usage Description in English to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see <see href="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionEn
{
@@ -185,7 +195,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// Whether or not to localize User Tracking Usage Description.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public bool UserTrackingUsageLocalizationEnabled
{
@@ -226,7 +236,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in German to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionDe
{
@@ -236,7 +246,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in Spanish to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionEs
{
@@ -246,7 +256,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in French to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionFr
{
@@ -256,7 +266,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in Japanese to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionJa
{
@@ -266,7 +276,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in Korean to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionKo
{
@@ -276,7 +286,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in Chinese (Simplified) to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionZhHans
{
@@ -286,7 +296,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// <summary>
/// A User Tracking Usage Description in Chinese (Traditional) to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
/// </summary>
public string UserTrackingUsageDescriptionZhHant
{
@@ -11,7 +11,7 @@ using UnityEngine;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
public class AppLovinMenuItems
public static class AppLovinMenuItems
{
/**
* The special characters at the end represent a shortcut for this action.
@@ -31,7 +31,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
[MenuItem("AppLovin/Documentation")]
private static void Documentation()
{
Application.OpenURL("https://developers.applovin.com/en/unity/overview/integration");
Application.OpenURL("https://support.axon.ai/en/max/unity/overview/integration");
}
[MenuItem("AppLovin/Contact Us")]
@@ -0,0 +1,614 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if !UNITY_2020_1_OR_NEWER
using System.Reflection;
#endif
using System.Xml.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEngine;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
[Serializable]
public class PackageInfo
{
// ReSharper disable InconsistentNaming - For JSON Deserialization
public string Name;
public string Version;
}
public interface IPackageManagerClient
{
IEnumerator AddNetwork(Network network, bool showImport);
void RemoveNetwork(Network network);
}
public static class AppLovinPackageManager
{
private const string AppLovinMediationAmazonAdapterDependenciesPath = "Amazon/Scripts/Mediations/AppLovinMediation/Editor/Dependencies.xml";
private static readonly IPackageManagerClient _upmPackageManager = new AppLovinUpmPackageManager();
private static readonly IPackageManagerClient _assetsPackageManager = new AppLovinAssetsPackageManager();
private static IPackageManagerClient PackageManagerClient
{
get
{
return AppLovinIntegrationManager.IsPluginInPackageManager ? _upmPackageManager : _assetsPackageManager;
}
}
internal static PluginData PluginData { get; set; }
/// <summary>
/// Checks whether or not an adapter with the given version or newer exists.
/// </summary>
/// <param name="adapterName">The name of the network (the root adapter folder name in "MaxSdk/Mediation/" folder.</param>
/// <param name="iosVersion">The min iOS adapter version to check for. Can be <c>null</c> if we want to check for any version.</param>
/// <param name="androidVersion">The min android adapter version to check for. Can be <c>null</c> if we want to check for any version.</param>
/// <returns><c>true</c> if an adapter with the min version is installed.</returns>
internal static bool IsAdapterInstalled(string adapterName, string iosVersion = null, string androidVersion = null)
{
var dependencyFilePathList = GetAssetPathListForExportPath("MaxSdk/Mediation/" + adapterName + "/Editor/Dependencies.xml");
if (dependencyFilePathList.Count <= 0) return false;
var currentVersion = GetCurrentVersions(dependencyFilePathList);
if (iosVersion != null)
{
var iosVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Ios, iosVersion);
if (iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Lesser)
{
return false;
}
}
if (androidVersion != null)
{
var androidVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Android, androidVersion);
if (androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Lesser)
{
return false;
}
}
return true;
}
/// <summary>
/// Checks whether an adapter is installed using the plugin data.
/// </summary>
/// <param name="pluginData">The plugin data to check for the adapter</param>
/// <param name="adapterName">The name of the network.</param>
/// <returns>Whether an adapter is installed in the plugin data</returns>
internal static bool IsAdapterInstalled(PluginData pluginData, string adapterName)
{
var network = pluginData.MediatedNetworks.Where(mediatedNetwork => mediatedNetwork.Name.Equals(adapterName)).ToList().FirstOrDefault();
var networkVersion = network != null ? network.CurrentVersions : null;
var currentVersion = networkVersion != null ? networkVersion.Unity : "";
return MaxSdkUtils.IsValidString(currentVersion);
}
/// <summary>
/// Gets the mediation networks that are currently installed in the project. If using UPM, checks
/// for networks in Packages folder and Mediation folder in case a custom adapter was added to the project.
/// </summary>
/// <returns>A list of the installed mediation network names.</returns>
internal static List<string> GetInstalledMediationNetworks()
{
var installedNetworks = new List<string>();
var installedNetworksInAssets = AppLovinAssetsPackageManager.GetInstalledMediationNetworks();
installedNetworks.AddRange(installedNetworksInAssets);
var installedNetworksInPackages = AppLovinUpmPackageManager.GetInstalledMediationNetworks();
installedNetworks.AddRange(installedNetworksInPackages);
if (IsAmazonAppLovinAdapterInstalled())
{
installedNetworks.Add("AmazonAdMarketplace");
}
return installedNetworks;
}
/// <summary>
/// Adds a network to the project.
/// </summary>
/// <param name="network">The network to add.</param>
/// <param name="showImport">Whether to show the import window (only for non UPM)</param>
internal static IEnumerator AddNetwork(Network network, bool showImport)
{
yield return PackageManagerClient.AddNetwork(network, showImport);
AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network));
}
/// <summary>
/// Removes a network from the project.
/// </summary>
/// <param name="network">The network to remove.</param>
internal static void RemoveNetwork(Network network)
{
PackageManagerClient.RemoveNetwork(network);
AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network));
}
#region Utility
/// <summary>
/// Gets the list of all asset paths for a given MAX plugin export path.
/// </summary>
/// <param name="exportPath">The actual exported path of the asset.</param>
/// <returns>The exported path of the MAX plugin asset or an empty list if the asset is not found.</returns>
private static List<string> GetAssetPathListForExportPath(string exportPath)
{
var assetLabelToFind = "l:al_max_export_path-" + MaxSdkUtils.NormalizeToUnityPath(exportPath);
var assetGuids = AssetDatabase.FindAssets(assetLabelToFind);
var assetPaths = new List<string>();
foreach (var assetGuid in assetGuids)
{
assetPaths.Add(AssetDatabase.GUIDToAssetPath(assetGuid));
}
return assetPaths.Count <= 0 ? new List<string>() : assetPaths;
}
/// <summary>
/// Updates the CurrentVersion fields for a given network data object.
/// </summary>
/// <param name="network">Network for which to update the current versions.</param>
internal static void UpdateCurrentVersions(Network network)
{
var assetPaths = GetAssetPathListForExportPath(network.DependenciesFilePath);
if (HasDuplicateAdapters(assetPaths))
{
ShowDeleteDuplicateAdapterPrompt(network);
}
var currentVersions = GetCurrentVersions(assetPaths);
network.CurrentVersions = currentVersions;
// If AppLovin mediation plugin, get the version from MaxSdk and the latest and current version comparison.
if (network.Name.Equals("APPLOVIN_NETWORK"))
{
network.CurrentVersions.Unity = MaxSdk.Version;
var unityVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Unity, network.LatestVersions.Unity);
var androidVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Android, network.LatestVersions.Android);
var iosVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Ios, network.LatestVersions.Ios);
// Overall version is same if all the current and latest (from db) versions are same.
if (unityVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal &&
androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal &&
iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal)
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Equal;
}
// One of the installed versions is newer than the latest versions which means that the publisher is on a beta version.
else if (unityVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater ||
androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater ||
iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater)
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Greater;
}
// We have a new version available if all Android, iOS and Unity has a newer version available in db.
else
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Lesser;
}
}
// For all other mediation adapters, get the version comparison using their Unity versions.
else
{
// If adapter is indeed installed, compare the current (installed) and the latest (from db) versions, so that we can determine if the publisher is on an older, current or a newer version of the adapter.
// If the publisher is on a newer version of the adapter than the db version, that means they are on a beta version.
if (MaxSdkUtils.IsValidString(currentVersions.Unity))
{
network.CurrentToLatestVersionComparisonResult = AppLovinIntegrationManagerUtils.CompareUnityMediationVersions(currentVersions.Unity, network.LatestVersions.Unity);
}
if (MaxSdkUtils.IsValidString(network.CurrentVersions.Unity) && AppLovinAutoUpdater.MinAdapterVersions.ContainsKey(network.Name))
{
var comparisonResult = AppLovinIntegrationManagerUtils.CompareUnityMediationVersions(network.CurrentVersions.Unity, AppLovinAutoUpdater.MinAdapterVersions[network.Name]);
// Requires update if current version is lower than the min required version.
network.RequiresUpdate = comparisonResult < 0;
}
else
{
// Reset value so that the Integration manager can hide the alert icon once adapter is updated.
network.RequiresUpdate = false;
}
}
}
/// <summary>
/// Checks whether a network has duplicate adapters installed in both the Assets folder and via UPM.
/// </summary>
/// <param name="dependencyPaths">The list of paths to the dependencies.xml files</param>
/// <returns><c>True</c> if there are adapters in both the Assets folder and installed via UPM</returns>
private static bool HasDuplicateAdapters(List<string> dependencyPaths)
{
var inPackagesFolder = dependencyPaths.Any(path => path.Contains("Packages"));
var inAssetsFolder = dependencyPaths.Any(path => path.Contains("Assets"));
return inPackagesFolder && inAssetsFolder;
}
/// <summary>
/// Displays a prompt informing the user that duplicate adapters were detected
/// and allows them to choose which version to keep.
/// </summary>
/// <param name="network">The network that has duplicate adapters installed.</param>
private static void ShowDeleteDuplicateAdapterPrompt(Network network)
{
var keepAssetsAdapter = EditorUtility.DisplayDialog("Duplicate Adapters Detected",
"The " + network.DisplayName + " adapter is installed in both the Assets folder and via UPM. Please choose which version to keep.",
"Keep Assets Folder Version",
"Keep UPM Version");
DeleteDuplicateAdapter(network, keepAssetsAdapter);
}
/// <summary>
/// Removes a duplicate adapter by either deleting it from the Assets folder
/// or uninstalling it from the Unity Package Manager (UPM).
/// </summary>
/// <param name="network">The network for which the duplicate adapter is being removed.</param>
/// <param name="keepAssetsAdapter">If <c>true</c>, retains the adapter in the Assets folder and removes the UPM version;
/// otherwise, deletes the adapter from the Assets folder.</param>
internal static void DeleteDuplicateAdapter(Network network, bool keepAssetsAdapter)
{
// Skip duplicate removal logic for our plugin.
if (network.Name.Equals("APPLOVIN_NETWORK")) return;
if (keepAssetsAdapter)
{
var appLovinManifest = AppLovinUpmManifest.Load();
AppLovinUpmPackageManager.RemovePackages(network, appLovinManifest);
appLovinManifest.Save();
}
else
{
foreach (var pluginFilePath in network.PluginFilePaths)
{
var filePath = Path.Combine(AppLovinIntegrationManager.MediationDirectory, pluginFilePath.Replace("MaxSdk/Mediation/", ""));
FileUtil.DeleteFileOrDirectory(filePath);
FileUtil.DeleteFileOrDirectory(filePath + ".meta");
}
}
AppLovinUpmPackageManager.ResolvePackageManager();
}
/// <summary>
/// Gets the current versions for a given network's dependency file paths. UPM will have multiple paths
/// for each network - one each for iOS and Android.
/// </summary>
/// <param name="dependencyPaths">A list of dependency file paths to extract current versions from.</param>
/// <returns>Current versions of a given network's dependency files.</returns>
private static Versions GetCurrentVersions(List<string> dependencyPaths)
{
var currentVersions = new Versions();
foreach (var dependencyPath in dependencyPaths)
{
GetCurrentVersion(currentVersions, dependencyPath);
}
if (currentVersions.Android != null && currentVersions.Ios != null)
{
currentVersions.Unity = "android_" + currentVersions.Android + "_ios_" + currentVersions.Ios;
}
else if (currentVersions.Android != null)
{
currentVersions.Unity = "android_" + currentVersions.Android;
}
else if (currentVersions.Ios != null)
{
currentVersions.Unity = "ios_" + currentVersions.Ios;
}
return currentVersions;
}
/// <summary>
/// Extracts the current version of a network from its dependency.xml file.
/// </summary>
/// <param name="currentVersions">The Versions object we are using.</param>
/// <param name="dependencyPath">The path to the dependency.xml file.</param>
private static void GetCurrentVersion(Versions currentVersions, string dependencyPath)
{
XDocument dependency;
try
{
dependency = XDocument.Load(dependencyPath);
}
#pragma warning disable 0168
catch (IOException exception)
#pragma warning restore 0168
{
// Couldn't find the dependencies file. The plugin is not installed.
return;
}
// <dependencies>
// <androidPackages>
// <androidPackage spec="com.applovin.mediation:network_name-adapter:1.2.3.4" />
// </androidPackages>
// <iosPods>
// <iosPod name="AppLovinMediationNetworkNameAdapter" version="2.3.4.5" />
// </iosPods>
// </dependencies>
string androidVersion = null;
string iosVersion = null;
var dependenciesElement = dependency.Element("dependencies");
if (dependenciesElement != null)
{
var androidPackages = dependenciesElement.Element("androidPackages");
if (androidPackages != null)
{
var adapterPackage = androidPackages.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("androidPackage")
&& element.FirstAttribute.Name.LocalName.Equals("spec")
&& element.FirstAttribute.Value.StartsWith("com.applovin"));
if (adapterPackage != null)
{
androidVersion = adapterPackage.FirstAttribute.Value.Split(':').Last();
// Hack alert: Some Android versions might have square brackets to force a specific version. Remove them if they are detected.
if (androidVersion.StartsWith("["))
{
androidVersion = androidVersion.Trim('[', ']');
}
}
}
var iosPods = dependenciesElement.Element("iosPods");
if (iosPods != null)
{
var adapterPod = iosPods.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("iosPod")
&& element.FirstAttribute.Name.LocalName.Equals("name")
&& element.FirstAttribute.Value.StartsWith("AppLovin"));
if (adapterPod != null)
{
iosVersion = adapterPod.Attributes().First(attribute => attribute.Name.LocalName.Equals("version")).Value;
}
}
}
if (androidVersion != null)
{
currentVersions.Android = androidVersion;
}
if (iosVersion != null)
{
currentVersions.Ios = iosVersion;
}
}
/// <summary>
/// Check for the Amazon AppLovin adapter in the project.
/// </summary>
/// <returns>Whether the AppLovin Adapter is installed through the Amazon SDK.</returns>
private static bool IsAmazonAppLovinAdapterInstalled()
{
string[] dependenciesFiles = AssetDatabase.FindAssets("t:TextAsset Dependencies", new[] {"Assets"})
.Select(AssetDatabase.GUIDToAssetPath)
.ToArray();
// Use regex to search for Amazon and then AppLovin in the file paths of the dependencies.xml files.
return dependenciesFiles.Any(filePath => filePath.Contains(AppLovinMediationAmazonAdapterDependenciesPath));
}
/// <summary>
/// Refresh assets and update current versions after a slight delay to allow for Client.Resolve to finish.
/// </summary>
/// <param name="network">The network that was just installed/removed.</param>
private static IEnumerator RefreshAssetsAtEndOfFrame(Network network)
{
yield return new WaitForEndOfFrame();
UpdateCurrentVersions(network);
AssetDatabase.Refresh();
}
#endregion
}
public class AppLovinUpmPackageManager : IPackageManagerClient
{
public const string PackageNamePrefixAppLovin = "com.applovin.mediation.ads";
private const string PackageNamePrefixNetwork = "com.applovin.mediation.adapters";
private const string PackageNamePrefixDsp = "com.applovin.mediation.dsp";
private const float TimeoutFetchPackageCollectionSeconds = 10f;
#if !UNITY_2020_1_OR_NEWER
private static Type packageManagerClientType;
private static MethodInfo packageManagerResolveMethod;
#endif
public static List<string> GetInstalledMediationNetworks()
{
// Return empty list if we failed to get the package list
var packageCollection = GetPackageCollectionSync(TimeoutFetchPackageCollectionSeconds);
if (packageCollection == null)
{
return new List<string>();
}
return packageCollection.Where(package => package.name.StartsWith(PackageNamePrefixNetwork) || package.name.StartsWith(PackageNamePrefixDsp))
.SelectMany(package => package.keywords)
.Where(keyword => keyword.StartsWith("dir:"))
.Select(keyword => keyword.Replace("dir:", ""))
.Distinct()
.ToList();
}
public IEnumerator AddNetwork(Network network, bool showImport)
{
var appLovinManifest = AppLovinUpmManifest.Load();
AddPackages(network, appLovinManifest);
appLovinManifest.Save();
// Remove any versions of the adapter in the Assets folder
AppLovinPackageManager.DeleteDuplicateAdapter(network, false);
ResolvePackageManager();
yield break;
}
public void RemoveNetwork(Network network)
{
var appLovinManifest = AppLovinUpmManifest.Load();
RemovePackages(network, appLovinManifest);
appLovinManifest.Save();
ResolvePackageManager();
}
/// <summary>
/// Adds a network's packages to the package manager removes any beta version that exists
/// </summary>
/// <param name="network">The network to add.</param>
/// <param name="appLovinManifest">The AppLovinUpmManifest instance to edit</param>
internal static void AddPackages(Network network, AppLovinUpmManifest appLovinManifest)
{
foreach (var packageInfo in network.Packages)
{
appLovinManifest.AddPackageDependency(packageInfo.Name, packageInfo.Version);
RemoveBetaPackage(packageInfo.Name, appLovinManifest);
}
}
/// <summary>
/// Removes a network's packages from the package manager
/// </summary>
/// <param name="network">The network to add.</param>
/// <param name="appLovinManifest">The AppLovinUpmManifest instance to edit</param>
internal static void RemovePackages(Network network, AppLovinUpmManifest appLovinManifest)
{
foreach (var packageInfo in network.Packages)
{
appLovinManifest.RemovePackageDependency(packageInfo.Name);
RemoveBetaPackage(packageInfo.Name, appLovinManifest);
}
}
/// <summary>
/// Removes the beta version of a package name
/// </summary>
/// <param name="packageName">The name of the package to remove a beta for</param>
/// <param name="appLovinManifest">The AppLovinUpmManifest instance to edit</param>
private static void RemoveBetaPackage(string packageName, AppLovinUpmManifest appLovinManifest)
{
var prefix = "";
if (packageName.Contains(PackageNamePrefixNetwork))
{
prefix = PackageNamePrefixNetwork;
}
else if (packageName.Contains(PackageNamePrefixDsp))
{
prefix = PackageNamePrefixDsp;
}
else if (packageName.Contains(PackageNamePrefixAppLovin))
{
prefix = PackageNamePrefixAppLovin;
}
else
{
return;
}
var betaPackageName = packageName.Replace(prefix, prefix + ".beta");
appLovinManifest.RemovePackageDependency(betaPackageName);
}
/// <summary>
/// Resolves the Unity Package Manager so any changes made to the manifest.json file are reflected in the Unity Editor.
/// </summary>
internal static void ResolvePackageManager()
{
#if UNITY_2020_1_OR_NEWER
Client.Resolve();
#else
packageManagerClientType = packageManagerClientType ?? typeof(Client);
if (packageManagerClientType != null)
{
packageManagerResolveMethod = packageManagerResolveMethod ?? packageManagerClientType.GetMethod("Resolve", BindingFlags.NonPublic | BindingFlags.Static);
}
if (packageManagerResolveMethod != null)
{
packageManagerResolveMethod.Invoke(null, null);
}
#endif
}
/// <summary>
/// Gets the PackageCollection from the Unity Package Manager synchronously.
/// </summary>
/// <param name="timeoutSeconds">How long to wait before exiting with a timeout error</param>
/// <returns></returns>
private static PackageCollection GetPackageCollectionSync(float timeoutSeconds = -1)
{
var request = Client.List();
// Just wait till the request is complete
var now = DateTime.Now;
while (!request.IsCompleted)
{
// Wait indefinitely if there is no timeout set.
if (timeoutSeconds < 0) continue;
var delta = DateTime.Now - now;
if (delta.TotalSeconds > timeoutSeconds)
{
MaxSdkLogger.UserError("Failed to list UPM packages: Timeout");
break;
}
}
if (!request.IsCompleted)
{
return null;
}
if (request.Status >= StatusCode.Failure)
{
MaxSdkLogger.UserError("Failed to list packages: " + request.Error.message);
return null;
}
return (request.Status == StatusCode.Success) ? request.Result : null;
}
}
public class AppLovinAssetsPackageManager : IPackageManagerClient
{
public static List<string> GetInstalledMediationNetworks()
{
var maxMediationDirectory = AppLovinIntegrationManager.MediationDirectory;
if (!Directory.Exists(maxMediationDirectory)) return new List<string>();
var mediationNetworkDirectories = Directory.GetDirectories(maxMediationDirectory);
return mediationNetworkDirectories.Select(Path.GetFileName).ToList();
}
public IEnumerator AddNetwork(Network network, bool showImport)
{
yield return AppLovinIntegrationManager.Instance.DownloadPlugin(network, showImport);
}
public void RemoveNetwork(Network network)
{
foreach (var pluginFilePath in network.PluginFilePaths)
{
var filePath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, pluginFilePath);
FileUtil.DeleteFileOrDirectory(filePath);
FileUtil.DeleteFileOrDirectory(filePath + ".meta");
}
}
}
}
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 409fe14211f31433da09f5b4bd5467b9
guid: 69faa9dfd9aac483daa24261a3e11206
labels:
- al_max
- al_max_export_path-MaxSdk/Scripts/MaxTargetingData.cs
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPackageManager.cs
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,166 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
/// <summary>
/// Moves our SDK Unity Plugin from under the Assets folder to the Unity Package Manager.
/// </summary>
public static class AppLovinPluginMigrationHelper
{
private const string ApplovinRegistryName = "AppLovin MAX Unity";
private const string ApplovinRegistryUrl = "https://unity.packages.applovin.com/";
private static readonly List<string> AppLovinRegistryScopes = new List<string>() {"com.applovin.mediation.ads", "com.applovin.mediation.adapters", "com.applovin.mediation.dsp"};
private const string OpenUpmRegistryName = "package.openupm.com";
private const string OpenUpmRegistryUrl = "https://package.openupm.com";
private static readonly List<string> OpenUpmRegistryScopes = new List<string>() {"com.google.external-dependency-manager"};
private static List<string> betaNetworkPluginFilePaths = new List<string>();
/// <summary>
/// Attempts to move the Unity plugin to UPM by adding the AppLovin scoped registry and dependencies to the manifest.
/// </summary>
/// <param name="pluginData">The Unity Plugin data for our sdk and mediation adapters.</param>
/// <param name="deleteExternalDependencyManager">Whether to delete the EDM folder under "Assets"</param>
internal static void MigrateToUnityPackageManager(PluginData pluginData, bool deleteExternalDependencyManager)
{
MaxSdkLogger.UserDebug("Moving AppLovin Unity Plugin to package manager");
if (deleteExternalDependencyManager)
{
DeleteExternalDependencyManager();
}
var appLovinManifest = AppLovinUpmManifest.Load();
MigrateAdapters(pluginData, appLovinManifest);
MigratePlugin(pluginData, appLovinManifest);
appLovinManifest.Save();
AppLovinUpmPackageManager.ResolvePackageManager();
DeletePluginFiles();
}
/// <summary>
/// Add all currently installed networks to the manifest.
/// </summary>
internal static void MigrateAdapters(PluginData pluginData, AppLovinUpmManifest appLovinManifest)
{
var allNetworks = pluginData.MediatedNetworks.Concat(pluginData.PartnerMicroSdks).ToArray();
betaNetworkPluginFilePaths.Clear();
// Add every currently installed network and separate it by android and iOS.
foreach (var network in allNetworks)
{
var currentVersion = network.CurrentVersions != null ? network.CurrentVersions.Unity : "";
if (string.IsNullOrEmpty(currentVersion)) continue;
if (currentVersion.Contains("beta"))
{
betaNetworkPluginFilePaths.AddRange(network.PluginFilePaths);
continue;
}
AppLovinUpmPackageManager.AddPackages(network, appLovinManifest);
}
}
/// <summary>
/// Add the AppLovin scoped registry to the manifest if it doesn't exist. Otherwise update it.
/// </summary>
private static void MigratePlugin(PluginData pluginData, AppLovinUpmManifest appLovinManifest)
{
appLovinManifest.AddOrUpdateRegistry(ApplovinRegistryName, ApplovinRegistryUrl, AppLovinRegistryScopes);
appLovinManifest.AddOrUpdateRegistry(OpenUpmRegistryName, OpenUpmRegistryUrl, OpenUpmRegistryScopes);
var appLovinVersion = pluginData.AppLovinMax.LatestVersions.Unity;
appLovinManifest.AddPackageDependency(AppLovinUpmPackageManager.PackageNamePrefixAppLovin, appLovinVersion);
}
#region Utility
/// <summary>
/// Delete the external dependency manager folder from the project.
/// </summary>
private static void DeleteExternalDependencyManager()
{
var externalDependencyManagerPath = Path.Combine(Application.dataPath, "ExternalDependencyManager");
FileUtil.DeleteFileOrDirectory(externalDependencyManagerPath);
FileUtil.DeleteFileOrDirectory(externalDependencyManagerPath + ".meta");
}
/// <summary>
/// Deletes all the files in the plugin directory except the AppLovinSettings.asset file.
/// </summary>
private static void DeletePluginFiles()
{
if (AppLovinIntegrationManager.IsPluginInPackageManager) return;
var pluginPath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, "MaxSdk");
var appLovinSettingsPath = Path.Combine(pluginPath, "Resources/AppLovinSettings.asset");
var appLovinResourcesDirectory = Path.Combine(pluginPath, "Resources");
var appLovinSettingsTempPath = Path.Combine(Path.GetTempPath(), "AppLovinSettings.asset");
var appLovinMediationTempPath = Path.Combine(Path.GetTempPath(), "Mediation");
// Ensure there aren't any errors when moving the files due to the directories/files already existing.
FileUtil.DeleteFileOrDirectory(appLovinSettingsTempPath);
FileUtil.DeleteFileOrDirectory(appLovinMediationTempPath);
var mediationAssetsDir = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, "MaxSdk/Mediation");
// Move the AppLovinSettings.asset file and any beta adapters to a temporary directory.
File.Move(appLovinSettingsPath, appLovinSettingsTempPath);
var adapterSaved = MoveBetaAdaptersIfNeeded(mediationAssetsDir, appLovinMediationTempPath);
// Move the meta file if the adapter was saved to save the asset labels
if (adapterSaved)
{
File.Move(mediationAssetsDir, appLovinMediationTempPath + ".meta");
}
// Delete the plugin directory and then move the AppLovinSettings.asset and beta adapters back.
FileUtil.DeleteFileOrDirectory(pluginPath);
Directory.CreateDirectory(appLovinResourcesDirectory);
File.Move(appLovinSettingsTempPath, appLovinSettingsPath);
MoveBetaAdaptersIfNeeded(appLovinMediationTempPath, mediationAssetsDir);
if (adapterSaved)
{
File.Move(appLovinMediationTempPath + ".meta", mediationAssetsDir);
}
FileUtil.DeleteFileOrDirectory(appLovinMediationTempPath);
}
/// <summary>
/// Moves the beta adapters from a source mediation directory to a destination mediation directory.
/// </summary>
/// <param name="sourceMediationDirectory">The directory containing the beta adapters to be moved.</param>
/// <param name="destinationMediationDirectory">The target directory where the beta adapters should be moved.</param>
private static bool MoveBetaAdaptersIfNeeded(string sourceMediationDirectory, string destinationMediationDirectory)
{
if (betaNetworkPluginFilePaths.Count == 0 || !Directory.Exists(sourceMediationDirectory)) return false;
var movedAdapter = false;
Directory.CreateDirectory(destinationMediationDirectory);
foreach (var pluginFilePath in betaNetworkPluginFilePaths)
{
var sourceDirectory = Path.Combine(sourceMediationDirectory, Path.GetFileName(pluginFilePath));
var destinationDirectory = Path.Combine(destinationMediationDirectory, Path.GetFileName(pluginFilePath));
if (Directory.Exists(sourceDirectory))
{
Directory.Move(sourceDirectory, destinationDirectory);
movedAdapter = true;
}
}
return movedAdapter;
}
#endregion
}
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 11288612632de49b99708cdee436692c
labels:
- al_max
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPluginMigrationHelper.cs
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -24,13 +24,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
public class AppLovinPostProcessAndroid : IPostGenerateGradleAndroidProject
{
#if UNITY_2019_3_OR_NEWER
private const string PropertyAndroidX = "android.useAndroidX";
private const string PropertyJetifier = "android.enableJetifier";
private const string EnableProperty = "=true";
#endif
private const string PropertyDexingArtifactTransform = "android.enableDexingArtifactTransform";
private const string DisableProperty = "=false";
private const string KeyMetaDataAppLovinVerboseLoggingOn = "applovin.sdk.verbose_logging";
private const string KeyMetaDataGoogleApplicationId = "com.google.android.gms.ads.APPLICATION_ID";
@@ -44,16 +40,12 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private const string AppLovinSettingsFileName = "applovin_settings.json";
private const string KeyTermsFlowSettings = "terms_flow_settings";
private const string KeyTermsFlowEnabled = "terms_flow_enabled";
private const string KeyTermsFlowTermsOfService = "terms_flow_terms_of_service";
private const string KeyTermsFlowPrivacyPolicy = "terms_flow_privacy_policy";
private const string KeySdkKey = "sdk_key";
private const string KeyConsentFlowSettings = "consent_flow_settings";
private const string KeyConsentFlowEnabled = "consent_flow_enabled";
private const string KeyConsentFlowTermsOfService = "consent_flow_terms_of_service";
private const string KeyConsentFlowPrivacyPolicy = "consent_flow_privacy_policy";
private const string KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR = "consent_flow_show_terms_and_privacy_policy_alert_in_gdpr";
private const string KeyConsentFlowDebugUserGeography = "consent_flow_debug_user_geography";
private const string KeyRenderOutsideSafeArea = "render_outside_safe_area";
@@ -72,26 +64,11 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static readonly XNamespace AndroidNamespace = "http://schemas.android.com/apk/res/android";
private static string PluginMediationDirectory
{
get
{
var pluginParentDir = AppLovinIntegrationManager.MediationSpecificPluginParentDirectory;
return Path.Combine(pluginParentDir, "MaxSdk/Mediation/");
}
}
public void OnPostGenerateGradleAndroidProject(string path)
{
#if UNITY_2019_3_OR_NEWER
var rootGradleBuildFilePath = Path.Combine(path, "../build.gradle");
var gradlePropertiesPath = Path.Combine(path, "../gradle.properties");
var gradleWrapperPropertiesPath = Path.Combine(path, "../gradle/wrapper/gradle-wrapper.properties");
#else
var rootGradleBuildFilePath = Path.Combine(path, "build.gradle");
var gradlePropertiesPath = Path.Combine(path, "gradle.properties");
var gradleWrapperPropertiesPath = Path.Combine(path, "gradle/wrapper/gradle-wrapper.properties");
#endif
UpdateGradleVersionsIfNeeded(gradleWrapperPropertiesPath, rootGradleBuildFilePath);
@@ -102,26 +79,13 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
var lines = File.ReadAllLines(gradlePropertiesPath);
#if UNITY_2019_3_OR_NEWER
// Add all properties except AndroidX, Jetifier, and DexingArtifactTransform since they may already exist. We will re-add them below.
gradlePropertiesUpdated.AddRange(lines.Where(line => !line.Contains(PropertyAndroidX) && !line.Contains(PropertyJetifier) && !line.Contains(PropertyDexingArtifactTransform)));
#else
// Add all properties except DexingArtifactTransform since it may already exist. We will re-add it below.
gradlePropertiesUpdated.AddRange(lines.Where(line => !line.Contains(PropertyDexingArtifactTransform)));
#endif
gradlePropertiesUpdated.AddRange(lines.Where(line => !line.Contains(PropertyAndroidX) && !line.Contains(PropertyJetifier)));
}
#if UNITY_2019_3_OR_NEWER
// Enable AndroidX and Jetifier properties
gradlePropertiesUpdated.Add(PropertyAndroidX + EnableProperty);
gradlePropertiesUpdated.Add(PropertyJetifier + EnableProperty);
#endif
// `DexingArtifactTransform` has been removed in Gradle 8+ which is the default Gradle version for Unity 6.
#if !UNITY_6000_0_OR_NEWER
// Disable dexing using artifact transform (it causes issues for ExoPlayer with Gradle plugin 3.5.0+)
gradlePropertiesUpdated.Add(PropertyDexingArtifactTransform + DisableProperty);
#endif
try
{
@@ -139,7 +103,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
public int callbackOrder
{
get { return int.MaxValue; }
get { return AppLovinPreProcess.CallbackOrder; }
}
private static void ProcessAndroidManifest(string path)
@@ -223,7 +187,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static void AddGoogleApplicationIdIfNeeded(XElement elementApplication, IEnumerable<XElement> metaDataElements)
{
if (!AppLovinIntegrationManager.IsAdapterInstalled("Google") && !AppLovinIntegrationManager.IsAdapterInstalled("GoogleAdManager")) return;
if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
var googleApplicationIdMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataGoogleApplicationId);
var appId = AppLovinSettings.Instance.AdMobAndroidAppId;
@@ -248,7 +212,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static void AddGoogleOptimizationFlagsIfNeeded(XElement elementApplication, IEnumerable<XElement> metaDataElements)
{
if (!AppLovinIntegrationManager.IsAdapterInstalled("Google") && !AppLovinIntegrationManager.IsAdapterInstalled("GoogleAdManager")) return;
if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
var googleOptimizeInitializationMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataGoogleOptimizeInitialization);
// If meta data doesn't exist, add it
@@ -267,7 +231,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static void DisableAutoInitIfNeeded(XElement elementApplication, IEnumerable<XElement> metaDataElements)
{
if (AppLovinIntegrationManager.IsAdapterInstalled("MobileFuse"))
if (AppLovinPackageManager.IsAdapterInstalled("MobileFuse"))
{
var mobileFuseMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataMobileFuseAutoInit);
// If MobileFuse meta data doesn't exist, add it
@@ -277,7 +241,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
}
if (AppLovinIntegrationManager.IsAdapterInstalled("MyTarget"))
if (AppLovinPackageManager.IsAdapterInstalled("MyTarget"))
{
var myTargetMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataMyTargetAutoInit);
// If MyTarget meta data doesn't exist, add it
@@ -358,15 +322,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
appLovinSdkSettings[KeySdkKey] = AppLovinSettings.Instance.SdkKey;
appLovinSdkSettings[KeyRenderOutsideSafeArea] = PlayerSettings.Android.renderOutsideSafeArea;
// Add the Consent/Terms flow settings if needed.
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled)
{
// Add the Terms and Privacy Policy flow settings if needed.
EnableConsentFlowIfNeeded(rawResourceDirectory, appLovinSdkSettings);
}
else
{
EnableTermsFlowIfNeeded(rawResourceDirectory, appLovinSdkSettings);
}
WriteAppLovinSettings(rawResourceDirectory, appLovinSdkSettings);
}
@@ -400,6 +357,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
consentFlowSettings[KeyConsentFlowTermsOfService] = termsOfServiceUrl;
}
consentFlowSettings[KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR] = AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR;
var debugUserGeography = AppLovinInternalSettings.Instance.DebugUserGeography;
if (debugUserGeography == MaxSdkBase.ConsentFlowUserGeography.Gdpr)
{
@@ -409,41 +368,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
applovinSdkSettings[KeyConsentFlowSettings] = consentFlowSettings;
}
private static void EnableTermsFlowIfNeeded(string rawResourceDirectory, Dictionary<string, object> applovinSdkSettings)
{
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled) return;
// Check if terms flow is enabled for this format. No need to create the applovin_consent_flow_settings.json if consent flow is disabled.
var consentFlowEnabled = AppLovinSettings.Instance.ConsentFlowEnabled;
var consentFlowPlatform = AppLovinSettings.Instance.ConsentFlowPlatform;
if (!consentFlowEnabled || (consentFlowPlatform != Platform.All && consentFlowPlatform != Platform.Android))
{
RemoveAppLovinSettingsRawResourceFileIfNeeded(rawResourceDirectory);
return;
}
var privacyPolicyUrl = AppLovinSettings.Instance.ConsentFlowPrivacyPolicyUrl;
if (string.IsNullOrEmpty(privacyPolicyUrl))
{
AppLovinIntegrationManager.ShowBuildFailureDialog("You cannot use the AppLovin SDK's consent flow without defining a Privacy Policy URL in the AppLovin Integration Manager.");
// No need to update the applovin_consent_flow_settings.json here. Default consent flow state will be determined on the SDK side.
return;
}
var consentFlowSettings = new Dictionary<string, object>();
consentFlowSettings[KeyTermsFlowEnabled] = consentFlowEnabled;
consentFlowSettings[KeyTermsFlowPrivacyPolicy] = privacyPolicyUrl;
var termsOfServiceUrl = AppLovinSettings.Instance.ConsentFlowTermsOfServiceUrl;
if (MaxSdkUtils.IsValidString(termsOfServiceUrl))
{
consentFlowSettings[KeyTermsFlowTermsOfService] = termsOfServiceUrl;
}
applovinSdkSettings[KeyTermsFlowSettings] = consentFlowSettings;
}
private static void WriteAppLovinSettingsRawResourceFile(string applovinSdkSettingsJson, string rawResourceDirectory)
{
if (!Directory.Exists(rawResourceDirectory))
@@ -22,20 +22,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
if (!AppLovinSettings.Instance.QualityServiceEnabled) return;
#if UNITY_2019_3_OR_NEWER
// On Unity 2019.3+, the path returned is the path to the unityLibrary's module.
// The AppLovin Quality Service buildscript closure related lines need to be added to the root build.gradle file.
var rootGradleBuildFilePath = Path.Combine(path, "../build.gradle");
var rootSettingsGradleFilePath = Path.Combine(path, "../settings.gradle");
// For 2022.2 and newer and 2021.3.41+
var qualityServiceAdded = AddPluginToRootGradleBuildFile(rootGradleBuildFilePath);
var appLovinRepositoryAdded = AddAppLovinRepository(rootSettingsGradleFilePath);
// For 2021.3.40 and older and 2022.0 - 2022.1.x
var buildScriptChangesAdded = AddQualityServiceBuildScriptLines(rootGradleBuildFilePath);
var failedToAddPlugin = !buildScriptChangesAdded && !(qualityServiceAdded && appLovinRepositoryAdded);
var failedToAddPlugin = !AddQualityServiceToRootGradleFile(path);
if (failedToAddPlugin)
{
MaxSdkLogger.UserWarning("Failed to add AppLovin Quality Service plugin to the gradle project.");
@@ -44,12 +31,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
// The plugin needs to be added to the application module (named launcher)
var applicationGradleBuildFilePath = Path.Combine(path, "../launcher/build.gradle");
#else
// If Gradle template is enabled, we would have already updated the plugin.
if (AppLovinIntegrationManager.GradleTemplateEnabled) return;
var applicationGradleBuildFilePath = Path.Combine(path, "build.gradle");
#endif
if (!File.Exists(applicationGradleBuildFilePath))
{
@@ -62,7 +43,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
public int callbackOrder
{
get { return int.MaxValue; }
get { return CallbackOrder; }
}
}
}
@@ -11,18 +11,14 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using AppLovinMax.Internal;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_2019_3_OR_NEWER
using UnityEditor.iOS.Xcode.Extensions;
#endif
using UnityEditor.iOS.Xcode;
using UnityEngine;
using Debug = UnityEngine.Debug;
using UnityEngine.Networking;
using VersionComparisonResult = MaxSdkUtils.VersionComparisonResult;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
@@ -36,9 +32,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
private const string OutputFileName = "AppLovinQualityServiceSetup.rb";
#if !UNITY_2019_3_OR_NEWER
private const string UnityMainTargetName = "Unity-iPhone";
#endif
// Use a priority of 90 to have AppLovin embed frameworks after Pods are installed (EDM finishes installing Pods at priority 60) and before Firebase Crashlytics runs their scripts (at priority 100).
private const int AppLovinEmbedFrameworksPriority = 90;
@@ -61,28 +54,20 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private const string KeyConsentFlowEnabled = "ConsentFlowEnabled";
private const string KeyConsentFlowTermsOfService = "ConsentFlowTermsOfService";
private const string KeyConsentFlowPrivacyPolicy = "ConsentFlowPrivacyPolicy";
private const string KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR = "ConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR";
private const string KeyConsentFlowDebugUserGeography = "ConsentFlowDebugUserGeography";
private const string KeyAppLovinSdkKeyToRemove = "AppLovinSdkKey";
private static readonly Regex PodfilePodLineRegex = new Regex("pod \'([^\']*)\'");
private static string PluginMediationDirectory
{
get
{
var pluginParentDir = AppLovinIntegrationManager.MediationSpecificPluginParentDirectory;
return Path.Combine(pluginParentDir, "MaxSdk/Mediation/");
}
}
/// <summary>
/// Adds AppLovin Quality Service to the iOS project once the project has been exported.
///
/// 1. Downloads the Quality Service ruby script.
/// 2. Runs the script using Ruby which integrates AppLovin Quality Service to the project.
/// </summary>
[PostProcessBuild(int.MaxValue)] // We want to run Quality Service script last.
[PostProcessBuild(AppLovinPreProcess.CallbackOrder)] // We want to run Quality Service script last.
public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
{
if (!AppLovinSettings.Instance.QualityServiceEnabled) return;
@@ -103,30 +88,22 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
return;
}
// Download the ruby script needed to install Quality Service
var downloadHandler = new DownloadHandlerFile(outputFilePath);
var postJson = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey);
var bodyRaw = Encoding.UTF8.GetBytes(postJson);
var uploadHandler = new UploadHandlerRaw(bodyRaw);
uploadHandler.contentType = "application/json";
using (var unityWebRequest = new UnityWebRequest("https://api2.safedk.com/v1/build/ios_setup2"))
var webRequestConfig = new WebRequestConfig()
{
unityWebRequest.method = UnityWebRequest.kHttpVerbPOST;
unityWebRequest.downloadHandler = downloadHandler;
unityWebRequest.uploadHandler = uploadHandler;
var operation = unityWebRequest.SendWebRequest();
DownloadHandler = new DownloadHandlerFile(outputFilePath),
JsonString = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey),
EndPoint = "https://api2.safedk.com/v1/build/ios_setup2",
RequestType = WebRequestType.Post,
};
// Wait for the download to complete or the request to timeout.
while (!operation.isDone) { }
webRequestConfig.Headers.Add("Content-Type", "application/json");
#if UNITY_2020_1_OR_NEWER
if (unityWebRequest.result != UnityWebRequest.Result.Success)
#else
if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
#endif
var maxWebRequest = new MaxWebRequest(webRequestConfig);
var webResponse = maxWebRequest.SendSync();
if (!webResponse.IsSuccess)
{
MaxSdkLogger.UserError("AppLovin Quality Service installation failed. Failed to download script with error: " + unityWebRequest.error);
MaxSdkLogger.UserError("AppLovin Quality Service installation failed. Failed to download script with error: " + webResponse.ErrorMessage);
return;
}
@@ -146,7 +123,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
MaxSdkLogger.UserDebug(result.Message);
}
}
[PostProcessBuild(AppLovinEmbedFrameworksPriority)]
public static void MaxPostProcessPbxProject(BuildTarget buildTarget, string buildPath)
@@ -155,32 +131,19 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var project = new PBXProject();
project.ReadFromFile(projectPath);
#if UNITY_2019_3_OR_NEWER
var unityMainTargetGuid = project.GetUnityMainTargetGuid();
var unityFrameworkTargetGuid = project.GetUnityFrameworkTargetGuid();
#else
var unityMainTargetGuid = project.TargetGuidByName(UnityMainTargetName);
var unityFrameworkTargetGuid = project.TargetGuidByName(UnityMainTargetName);
#endif
EmbedDynamicLibrariesIfNeeded(buildPath, project, unityMainTargetGuid);
var internalSettingsEnabled = AppLovinInternalSettings.Instance.ConsentFlowEnabled;
var userTrackingUsageDescriptionDe = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe : AppLovinSettings.Instance.UserTrackingUsageDescriptionDe;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionDe, "de", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionEn = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn : AppLovinSettings.Instance.UserTrackingUsageDescriptionEn;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionEn, "en", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionEs = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs : AppLovinSettings.Instance.UserTrackingUsageDescriptionEs;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionEs, "es", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionFr = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr : AppLovinSettings.Instance.UserTrackingUsageDescriptionFr;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionFr, "fr", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionJa = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa : AppLovinSettings.Instance.UserTrackingUsageDescriptionJa;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionJa, "ja", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionKo = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo : AppLovinSettings.Instance.UserTrackingUsageDescriptionKo;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionKo, "ko", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionZhHans = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans : AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHans;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionZhHans, "zh-Hans", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionZhHant = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant : AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHant;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionZhHant, "zh-Hant", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe, "de", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn, "en", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs, "es", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr, "fr", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa, "ja", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo, "ko", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans, "zh-Hans", buildPath, project, unityMainTargetGuid);
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant, "zh-Hant", buildPath, project, unityMainTargetGuid);
AddSwiftSupport(buildPath, project, unityFrameworkTargetGuid, unityMainTargetGuid);
AddYandexSettingsIfNeeded(project, unityMainTargetGuid);
@@ -197,23 +160,11 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var dynamicLibraryPathsToEmbed = GetDynamicLibraryPathsToEmbed(podsDirectory, buildPath);
if (dynamicLibraryPathsToEmbed == null || dynamicLibraryPathsToEmbed.Count == 0) return;
#if UNITY_2019_3_OR_NEWER
foreach (var dynamicLibraryPath in dynamicLibraryPathsToEmbed)
{
var fileGuid = project.AddFile(dynamicLibraryPath, dynamicLibraryPath);
project.AddFileToEmbedFrameworks(targetGuid, fileGuid);
}
#else
string runpathSearchPaths;
runpathSearchPaths = project.GetBuildPropertyForAnyConfig(targetGuid, "LD_RUNPATH_SEARCH_PATHS");
runpathSearchPaths += string.IsNullOrEmpty(runpathSearchPaths) ? "" : " ";
// Check if runtime search paths already contains the required search paths for dynamic libraries.
if (runpathSearchPaths.Contains("@executable_path/Frameworks")) return;
runpathSearchPaths += "@executable_path/Frameworks";
project.SetBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", runpathSearchPaths);
#endif
}
/// <summary>
@@ -333,13 +284,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var currentIosVersion = network.CurrentVersions.Ios;
if (string.IsNullOrEmpty(currentIosVersion)) return false;
var minIosVersion = libraryToEmbed.MinVersion;
var maxIosVersion = libraryToEmbed.MaxVersion;
var greaterThanOrEqualToMinVersion = string.IsNullOrEmpty(minIosVersion) || MaxSdkUtils.CompareVersions(currentIosVersion, minIosVersion) != VersionComparisonResult.Lesser;
var lessThanOrEqualToMaxVersion = string.IsNullOrEmpty(maxIosVersion) || MaxSdkUtils.CompareVersions(currentIosVersion, maxIosVersion) != VersionComparisonResult.Greater;
return greaterThanOrEqualToMinVersion && lessThanOrEqualToMaxVersion;
return MaxSdkUtils.IsVersionInRange(currentIosVersion, libraryToEmbed.MinVersion, libraryToEmbed.MaxVersion);
}
private static List<string> GetDynamicLibraryPathsInProjectToEmbed(string podsDirectory, List<string> dynamicLibrariesToEmbed)
@@ -443,11 +388,8 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
if (string.IsNullOrEmpty(localizedUserTrackingDescription)) return true;
var settings = AppLovinSettings.Instance;
var internalSettings = AppLovinInternalSettings.Instance;
return (!internalSettings.ConsentFlowEnabled || !internalSettings.UserTrackingUsageLocalizationEnabled)
&& (!settings.ConsentFlowEnabled || !settings.UserTrackingUsageLocalizationEnabled);
return !internalSettings.ConsentFlowEnabled || !internalSettings.UserTrackingUsageLocalizationEnabled;
}
private static void AddSwiftSupport(string buildPath, PBXProject project, string unityFrameworkTargetGuid, string unityMainTargetGuid)
@@ -467,9 +409,19 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
project.SetBuildProperty(unityFrameworkTargetGuid, "SWIFT_VERSION", "5.0");
}
// Enable Swift modules
project.AddBuildProperty(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES", "YES");
project.AddBuildProperty(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
// Some publishers may configure these settings in their own post-processing scripts.
// Only set them if they haven't already been defined to avoid overwriting publisher-defined values.
var enableModules = project.GetBuildPropertyForAnyConfig(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES");
if (string.IsNullOrEmpty(enableModules))
{
project.SetBuildProperty(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES", "YES");
}
var alwaysEmbedSwiftLibraries = project.GetBuildPropertyForAnyConfig(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES");
if (string.IsNullOrEmpty(alwaysEmbedSwiftLibraries))
{
project.SetBuildProperty(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
}
}
private static void CreateSwiftFile(string swiftFilePath)
@@ -487,44 +439,36 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
}
[PostProcessBuild(int.MaxValue)]
[PostProcessBuild(AppLovinPreProcess.CallbackOrder)]
public static void MaxPostProcessPlist(BuildTarget buildTarget, string path)
{
var plistPath = Path.Combine(path, "Info.plist");
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
SetAttributionReportEndpointIfNeeded(plist);
RemoveAttributionReportEndpointIfNeeded(plist);
EnableVerboseLoggingIfNeeded(plist);
AddGoogleApplicationIdIfNeeded(plist);
AddSdkSettings(plist, path);
EnableTermsFlowIfNeeded(plist);
AddSkAdNetworksInfoIfNeeded(plist);
RemoveSdkKeyIfNeeded(plist);
plist.WriteToFile(plistPath);
}
private static void SetAttributionReportEndpointIfNeeded(PlistDocument plist)
{
if (AppLovinSettings.Instance.SetAttributionReportEndpoint)
{
plist.root.SetString("NSAdvertisingAttributionReportEndpoint", AppLovinAdvertisingAttributionEndpoint);
}
else
private static void RemoveAttributionReportEndpointIfNeeded(PlistDocument plist)
{
PlistElement attributionReportEndPoint;
plist.root.values.TryGetValue("NSAdvertisingAttributionReportEndpoint", out attributionReportEndPoint);
// Check if we had previously set the attribution endpoint and un-set it.
if (attributionReportEndPoint != null && AppLovinAdvertisingAttributionEndpoint.Equals(attributionReportEndPoint.AsString()))
{
// We no longer support this feature. Check if we had previously set the attribution endpoint and un-set it.
if (attributionReportEndPoint == null || !AppLovinAdvertisingAttributionEndpoint.Equals(attributionReportEndPoint.AsString())) return;
MaxSdkLogger.UserWarning("Global SKAdNetwork postback forwarding is no longer supported by AppLovin. Removing AppLovin Advertising Attribution Endpoint from Info.plist.");
plist.root.values.Remove("NSAdvertisingAttributionReportEndpoint");
}
}
}
private static void EnableVerboseLoggingIfNeeded(PlistDocument plist)
{
@@ -543,7 +487,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static void AddGoogleApplicationIdIfNeeded(PlistDocument plist)
{
if (!AppLovinIntegrationManager.IsAdapterInstalled("Google") && !AppLovinIntegrationManager.IsAdapterInstalled("GoogleAdManager")) return;
if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
const string googleApplicationIdentifier = "GADApplicationIdentifier";
var appId = AppLovinSettings.Instance.AdMobIosAppId;
@@ -559,7 +503,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static void AddYandexSettingsIfNeeded(PBXProject project, string unityMainTargetGuid)
{
if (!AppLovinIntegrationManager.IsAdapterInstalled("Yandex")) return;
if (!AppLovinPackageManager.IsAdapterInstalled("Yandex")) return;
if (MaxSdkUtils.CompareVersions(PlayerSettings.iOS.targetOSVersionString, "12.0") == MaxSdkUtils.VersionComparisonResult.Lesser)
{
@@ -591,13 +535,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var project = new PBXProject();
project.ReadFromFile(projectPath);
#if UNITY_2019_3_OR_NEWER
var unityMainTargetGuid = project.GetUnityMainTargetGuid();
#else
var unityMainTargetGuid = project.TargetGuidByName(UnityMainTargetName);
#endif
var guid = project.AddFile(AppLovinSettingsPlistFileName, AppLovinSettingsPlistFileName, PBXSourceTree.Source);
var guid = project.AddFile(AppLovinSettingsPlistFileName, AppLovinSettingsPlistFileName);
project.AddFileToBuild(unityMainTargetGuid, guid);
project.WriteToFile(projectPath);
}
@@ -628,6 +568,9 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
consentFlowInfoRoot.SetString(KeyConsentFlowTermsOfService, termsOfServiceUrl);
}
var shouldShowTermsAndPrivacyPolicyAlertInGdpr = AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR;
consentFlowInfoRoot.SetBoolean(KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR, shouldShowTermsAndPrivacyPolicyAlertInGdpr);
var debugUserGeography = AppLovinInternalSettings.Instance.DebugUserGeography;
if (debugUserGeography == MaxSdkBase.ConsentFlowUserGeography.Gdpr)
{
@@ -637,40 +580,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
infoPlist.root.SetString("NSUserTrackingUsageDescription", userTrackingUsageDescription);
}
private static void EnableTermsFlowIfNeeded(PlistDocument plist)
{
// Check if terms flow is enabled. No need to update info.plist if consent flow is disabled.
var consentFlowEnabled = AppLovinSettings.Instance.ConsentFlowEnabled;
if (!consentFlowEnabled) return;
// Check if terms flow is enabled for this format.
var consentFlowPlatform = AppLovinSettings.Instance.ConsentFlowPlatform;
if (consentFlowPlatform != Platform.All && consentFlowPlatform != Platform.iOS) return;
var userTrackingUsageDescription = AppLovinSettings.Instance.UserTrackingUsageDescriptionEn;
var privacyPolicyUrl = AppLovinSettings.Instance.ConsentFlowPrivacyPolicyUrl;
if (string.IsNullOrEmpty(userTrackingUsageDescription) || string.IsNullOrEmpty(privacyPolicyUrl))
{
AppLovinIntegrationManager.ShowBuildFailureDialog("You cannot use the AppLovin SDK's consent flow without defining a Privacy Policy URL and the `User Tracking Usage Description` in the AppLovin Integration Manager. \n\n" +
"Both values must be included to enable the SDK's consent flow.");
// No need to update the info.plist here. Default consent flow state will be determined on the SDK side.
return;
}
var consentFlowInfoRoot = plist.root.CreateDict("AppLovinConsentFlowInfo");
consentFlowInfoRoot.SetBoolean("AppLovinConsentFlowEnabled", true);
consentFlowInfoRoot.SetString("AppLovinConsentFlowPrivacyPolicy", privacyPolicyUrl);
var termsOfServiceUrl = AppLovinSettings.Instance.ConsentFlowTermsOfServiceUrl;
if (!string.IsNullOrEmpty(termsOfServiceUrl))
{
consentFlowInfoRoot.SetString("AppLovinConsentFlowTermsOfService", termsOfServiceUrl);
}
plist.root.SetString("NSUserTrackingUsageDescription", userTrackingUsageDescription);
}
private static void AddSkAdNetworksInfoIfNeeded(PlistDocument plist)
{
var skAdNetworkData = GetSkAdNetworkData();
@@ -731,53 +640,39 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static SkAdNetworkData GetSkAdNetworkData()
{
var uriBuilder = new UriBuilder("https://unity.applovin.com/max/1.0/skadnetwork_ids");
// Get the list of installed ad networks to be passed up
var maxMediationDirectory = PluginMediationDirectory;
if (Directory.Exists(maxMediationDirectory))
{
var mediationNetworkDirectories = Directory.GetDirectories(maxMediationDirectory);
var installedNetworks = mediationNetworkDirectories.Select(Path.GetFileName).ToList();
if (AppLovinSettings.Instance.AddApsSkAdNetworkIds)
{
installedNetworks.Add("AmazonAdMarketplace");
}
var installedNetworks = AppLovinPackageManager.GetInstalledMediationNetworks();
var uriBuilder = new UriBuilder("https://unity.applovin.com/max/1.0/skadnetwork_ids");
var adNetworks = string.Join(",", installedNetworks.ToArray());
if (!string.IsNullOrEmpty(adNetworks))
if (MaxSdkUtils.IsValidString(adNetworks))
{
uriBuilder.Query += string.Format("ad_networks={0}", adNetworks);
}
}
using (var unityWebRequest = UnityWebRequest.Get(uriBuilder.ToString()))
var webRequestConfig = new WebRequestConfig()
{
var operation = unityWebRequest.SendWebRequest();
// Wait for the download to complete or the request to timeout.
while (!operation.isDone) { }
EndPoint = uriBuilder.ToString()
};
#if UNITY_2020_1_OR_NEWER
if (unityWebRequest.result != UnityWebRequest.Result.Success)
#else
if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
#endif
var maxWebRequest = new MaxWebRequest(webRequestConfig);
var webResponse = maxWebRequest.SendSync();
if (!webResponse.IsSuccess)
{
MaxSdkLogger.UserError("Failed to retrieve SKAdNetwork IDs with error: " + unityWebRequest.error);
MaxSdkLogger.UserError("Failed to retrieve SKAdNetwork IDs with error: " + webResponse.ErrorMessage);
return new SkAdNetworkData();
}
try
{
return JsonUtility.FromJson<SkAdNetworkData>(unityWebRequest.downloadHandler.text);
return JsonUtility.FromJson<SkAdNetworkData>(webResponse.ResponseMessage);
}
catch (Exception exception)
{
MaxSdkLogger.UserError("Failed to parse data '" + unityWebRequest.downloadHandler.text + "' with exception: " + exception);
MaxSdkLogger.UserError("Failed to parse data '" + webResponse.ResponseMessage + "' with exception: " + exception);
return new SkAdNetworkData();
}
}
}
private static void RemoveSdkKeyIfNeeded(PlistDocument plist)
{
@@ -7,16 +7,19 @@
//
using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using UnityEngine;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
public abstract class AppLovinPreProcess
{
// Use a slightly lower value than max value so pubs have the option to run a post process script after ours.
internal const int CallbackOrder = int.MaxValue - 10;
private const string AppLovinDependenciesFileExportPath = "MaxSdk/AppLovin/Editor/Dependencies.xml";
private const string ElementNameDependencies = "dependencies";
private static readonly XmlWriterSettings DependenciesFileXmlWriterSettings = new XmlWriterSettings
{
@@ -26,80 +29,154 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
NewLineHandling = NewLineHandling.Replace
};
protected static string AppLovinDependenciesFilePath
{
get { return AppLovinIntegrationManager.IsPluginInPackageManager ? Path.Combine("Assets", AppLovinDependenciesFileExportPath) : MaxSdkUtils.GetAssetPathForExportPath(AppLovinDependenciesFileExportPath); }
}
/// <summary>
/// Adds a string into AppLovin's Dependencies.xml file inside the containerElementString if it doesn't exist
/// Gets the AppLovin Dependencies.xml file. If `createIfNotExists` is true, a new file will be created if one does not exist.
/// </summary>
/// <param name="lineToAdd">The line you want to add into the xml file</param>
/// <param name="containerElementString">The root XML element under which to add the line. For example, to add a new dependency to Android, pass in "androidPackages"</param>
protected static void TryAddStringToDependencyFile(string lineToAdd, string containerElementString)
/// <param name="path">The path to the AppLovin Dependencies.xml file</param>
/// <param name="createIfNotExists">Whether to create a new Dependencies.xml file if one does not exist</param>
/// <returns></returns>
protected static XDocument GetAppLovinDependenciesFile(string path, bool createIfNotExists = false)
{
try
{
var dependenciesFilePath = MaxSdkUtils.GetAssetPathForExportPath(AppLovinDependenciesFileExportPath);
var dependencies = XDocument.Load(dependenciesFilePath);
// Get the container where we are going to insert the line
var containerElement = dependencies.Descendants(containerElementString).FirstOrDefault();
if (containerElement == null)
if (File.Exists(path))
{
MaxSdkLogger.E(containerElementString + " not found in Dependencies.xml file");
return;
return XDocument.Load(path);
}
var elementToAdd = XElement.Parse(lineToAdd);
// Check if the xml file doesn't already contain the string.
if (containerElement.Elements().Any(element => XNode.DeepEquals(element, elementToAdd))) return;
// Append the new element to the container element
containerElement.Add(elementToAdd);
using (var xmlWriter = XmlWriter.Create(dependenciesFilePath, DependenciesFileXmlWriterSettings))
if (createIfNotExists)
{
dependencies.Save(xmlWriter);
return new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
new XElement(ElementNameDependencies));
}
}
catch (Exception exception)
{
MaxSdkLogger.UserWarning("Google CMP will not function. Unable to add string to dependency file due to exception: " + exception.Message);
MaxSdkLogger.E("Unable to load Dependencies file due to exception: " + exception.Message);
}
return null;
}
/// <summary>
/// Updates a dependency if it exists, otherwise adds a new dependency.
/// </summary>
/// <param name="dependenciesDocument">The dependencies document we are writing to</param>
/// <param name="parentTag">The parent tag that we want to search for the dependency. For example, to add a new dependency to Android, pass in "androidPackages"</param>
/// <param name="elementTag">The element we are looking to update/add. For example, to add a new dependency to Android, pass in "androidPackage"</param>
/// <param name="matchAttribute">The attribute name we want in the dependency. For example, to add something to the spec attribute, pass in "spec" </param>
/// <param name="matchValuePrefix">The attribute value prefix we are looking to replace. For example, "com.google.android.ump:user-messaging-platform"</param>
/// <param name="newDependency">The new dependency we want to add.</param>
protected static void AddOrUpdateDependency(
XDocument dependenciesDocument,
string parentTag,
string elementTag,
string matchAttribute,
string matchValuePrefix,
XElement newDependency)
{
var parentElement = dependenciesDocument.Root.Element(parentTag);
if (parentElement == null)
{
parentElement = new XElement(parentTag);
dependenciesDocument.Root.Add(parentElement);
}
// Check if a dependency exists that matches the attributes name and value
var existingElement = parentElement.Elements(elementTag)
.FirstOrDefault(element =>
{
var attr = element.Attribute(matchAttribute);
return attr != null && attr.Value.StartsWith(matchValuePrefix, StringComparison.OrdinalIgnoreCase);
});
if (existingElement != null)
{
foreach (var attr in newDependency.Attributes())
{
existingElement.SetAttributeValue(attr.Name, attr.Value);
}
}
else
{
parentElement.Add(newDependency);
}
}
/// <summary>
/// Removes a string from AppLovin's Dependencies.xml file inside the containerElementString if it exists
/// Removes a dependency from an xml file.
/// </summary>
/// <param name="lineToRemove">The line you want to remove from the xml file</param>
/// <param name="containerElementString">The root XML element from which to remove the line. For example, to remove an Android dependency, pass in "androidPackages"</param>
protected static void TryRemoveStringFromDependencyFile(string lineToRemove, string containerElementString)
/// <param name="doc">The xml file to remove a dependency from</param>
/// <param name="parentTag">The parent tag that we want to search for the dependency to remove. For example: "androidPackages"</param>
/// <param name="elementTag">The element we are looking to remove. For example: "androidPackage"</param>
/// <param name="matchAttribute">The attribute name we want to remove. For example: "spec" </param>
/// <param name="matchValuePrefix">The attribute value prefix we are looking to replace. For example: "com.google.android.ump:user-messaging-platform"</param>
/// <returns>True if the dependency was removed successfully, otherwise return false.</returns>
protected static bool RemoveDependency(
XDocument doc,
string parentTag,
string elementTag,
string matchAttribute,
string matchValuePrefix)
{
var root = doc.Root;
if (root == null) return false;
var parentElement = root.Element(parentTag);
if (parentElement == null) return false;
XElement toRemove = null;
foreach (var e in parentElement.Elements(elementTag))
{
var attr = e.Attribute(matchAttribute);
if (attr != null && attr.Value.StartsWith(matchValuePrefix))
{
toRemove = e;
break;
}
}
if (toRemove == null) return false;
toRemove.Remove();
return true;
}
/// <summary>
/// Saves an xml file.
/// </summary>
/// <param name="doc">The document to save</param>
/// <param name="path">The path to the document to save</param>
/// <returns>Returns true if the file was saved successfully</returns>
protected static bool SaveDependenciesFile(XDocument doc, string path)
{
try
{
var dependenciesFilePath = MaxSdkUtils.GetAssetPathForExportPath(AppLovinDependenciesFileExportPath);
var dependencies = XDocument.Load(dependenciesFilePath);
var containerElement = dependencies.Descendants(containerElementString).FirstOrDefault();
if (containerElement == null)
// Ensure directory exists before saving the file
var directory = Path.GetDirectoryName(path);
if (MaxSdkUtils.IsValidString(directory))
{
MaxSdkLogger.E(containerElementString + " not found in Dependencies.xml file");
return;
// Does nothing if directory already exists
Directory.CreateDirectory(directory);
}
// Check if the dependency line exists.
var elementToFind = XElement.Parse(lineToRemove);
var existingElement = containerElement.Elements().FirstOrDefault(element => XNode.DeepEquals(element, elementToFind));
if (existingElement == null) return;
existingElement.Remove();
using (var xmlWriter = XmlWriter.Create(dependenciesFilePath, DependenciesFileXmlWriterSettings))
using (var xmlWriter = XmlWriter.Create(path, DependenciesFileXmlWriterSettings))
{
dependencies.Save(xmlWriter);
doc.Save(xmlWriter);
return true;
}
}
catch (Exception exception)
{
MaxSdkLogger.UserWarning("Unable to remove string from dependency file due to exception: " + exception.Message);
}
MaxSdkLogger.E("Unable to save Dependencies file due to exception: " + exception.Message);
}
return false;
}
}
}
@@ -8,6 +8,7 @@
#if UNITY_ANDROID
using System.Xml.Linq;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
@@ -18,9 +19,11 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
public class AppLovinPreProcessAndroid : AppLovinProcessGradleBuildFile, IPreprocessBuildWithReport
{
private const string UmpLegacyDependencyLine = "<androidPackage spec=\"com.google.android.ump:user-messaging-platform:2.1.0\" />";
private const string UmpDependencyLine = "<androidPackage spec=\"com.google.android.ump:user-messaging-platform:2.+\" />";
private const string AndroidPackagesContainerElementString = "androidPackages";
private const string ElementNameAndroidPackages = "androidPackages";
private const string ElementNameAndroidPackage = "androidPackage";
private const string AttributeNameSpec = "spec";
private const string UmpDependencyPackage = "com.google.android.ump:user-messaging-platform:";
private const string UmpDependencyVersion = "4.0.0";
public void OnPreprocessBuild(BuildReport report)
{
@@ -33,33 +36,74 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
// We can only process gradle template file here. If it is not available, we will try again in post build on Unity IDEs newer than 2018_2 (see AppLovinPostProcessGradleProject).
if (!AppLovinIntegrationManager.GradleTemplateEnabled) return;
#if UNITY_2019_3_OR_NEWER
// The publisher could be migrating from older Unity versions to 2019_3 or newer.
// If so, we should delete the plugin from the template. The plugin will be added to the project's application module in the post processing script (AppLovinPostProcessGradleProject).
RemoveAppLovinQualityServiceOrSafeDkPlugin(AppLovinIntegrationManager.GradleTemplatePath);
#else
AddAppLovinQualityServicePlugin(AppLovinIntegrationManager.GradleTemplatePath);
#endif
}
private static void AddGoogleCmpDependencyIfNeeded()
{
// Remove the legacy fixed UMP version if it exists, we'll add the dependency with a dynamic version below.
TryRemoveStringFromDependencyFile(UmpLegacyDependencyLine, AndroidPackagesContainerElementString);
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled)
{
TryAddStringToDependencyFile(UmpDependencyLine, AndroidPackagesContainerElementString);
var umpPackage = new XElement(ElementNameAndroidPackage,
new XAttribute(AttributeNameSpec, UmpDependencyPackage + UmpDependencyVersion));
var success = AddOrUpdateAndroidDependency(UmpDependencyPackage, umpPackage );
if (!success)
{
MaxSdkLogger.UserWarning("Google CMP will not function. Unable to add user-messaging-platform dependency.");
}
}
else
{
TryRemoveStringFromDependencyFile(UmpDependencyLine, AndroidPackagesContainerElementString);
RemoveAndroidDependency(UmpDependencyPackage);
}
}
/// <summary>
/// Adds or updates an Android dependency in the AppLovin Dependencies.xml file.
/// </summary>
/// <param name="package">The package that we are trying to update</param>
/// <param name="newDependency">The new dependency to add if it doesn't exist</param>
/// <returns>Returns true if the file was successfully edited</returns>
private static bool AddOrUpdateAndroidDependency(string package, XElement newDependency)
{
var dependenciesFilePath = AppLovinDependenciesFilePath;
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath, AppLovinIntegrationManager.IsPluginInPackageManager);
if (dependenciesDocument == null) return false;
AddOrUpdateDependency(dependenciesDocument,
ElementNameAndroidPackages,
ElementNameAndroidPackage,
AttributeNameSpec,
package,
newDependency);
return SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
}
/// <summary>
/// Removed an android dependency from the AppLovin Dependencies.xml file.
/// </summary>
/// <param name="package">The package to remove</param>
private static void RemoveAndroidDependency(string package)
{
var dependenciesFilePath = AppLovinDependenciesFilePath;
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath);
if (dependenciesDocument == null) return;
var removed = RemoveDependency(dependenciesDocument,
ElementNameAndroidPackages,
ElementNameAndroidPackage,
AttributeNameSpec,
package);
if (!removed) return;
SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
}
public int callbackOrder
{
get { return int.MaxValue; }
get { return CallbackOrder; }
}
}
}
@@ -8,6 +8,7 @@
#if UNITY_IOS
using System.Xml.Linq;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
@@ -20,28 +21,77 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
AddGoogleCmpDependencyIfNeeded();
}
private const string UmpLegacyDependencyLine = "<iosPod name=\"GoogleUserMessagingPlatform\" version=\"2.1.0\" />";
private const string UmpDependencyLine = "<iosPod name=\"GoogleUserMessagingPlatform\" version=\"~&gt; 2.1\" />";
private const string IosPodsContainerElementString = "iosPods";
private const string ElementNameIosPods = "iosPods";
private const string ElementNameIosPod = "iosPod";
private const string AttributeNameName = "name";
private const string AttributeNameVersion = "version";
private const string UmpDependencyPod = "GoogleUserMessagingPlatform";
private const string UmpDependencyVersion = "~> 3.1";
private static void AddGoogleCmpDependencyIfNeeded()
{
// Remove the legacy fixed UMP version if it exists, we'll add the dependency with a dynamic version below.
TryRemoveStringFromDependencyFile(UmpLegacyDependencyLine, IosPodsContainerElementString);
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled)
{
TryAddStringToDependencyFile(UmpDependencyLine, IosPodsContainerElementString);
var umpDependency = new XElement(ElementNameIosPod,
new XAttribute(AttributeNameName, UmpDependencyPod),
new XAttribute(AttributeNameVersion, UmpDependencyVersion));
var success = AddOrUpdateIosDependency(UmpDependencyPod, umpDependency);
if (!success)
{
MaxSdkLogger.UserWarning("Google CMP will not function. Unable to add GoogleUserMessagingPlatform dependency.");
}
}
else
{
TryRemoveStringFromDependencyFile(UmpDependencyLine, IosPodsContainerElementString);
RemoveIosDependency(UmpDependencyPod);
}
}
/// <summary>
/// Adds or updates an iOS pod in the AppLovin Dependencies.xml file.
/// </summary>
/// <param name="pod">The pod that we are trying to update</param>
/// <param name="newDependency">The new dependency to add if it doesn't exist</param>
/// <returns>Returns true if the file was successfully edited</returns>
private static bool AddOrUpdateIosDependency(string pod, XElement newDependency)
{
var dependenciesFilePath = AppLovinDependenciesFilePath;
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath, AppLovinIntegrationManager.IsPluginInPackageManager);
if (dependenciesDocument == null) return false;
AddOrUpdateDependency(dependenciesDocument,
ElementNameIosPods,
ElementNameIosPod,
AttributeNameName,
pod,
newDependency);
return SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
}
/// <summary>
/// Removed an iOS pod from the AppLovin Dependencies.xml file.
/// </summary>
/// <param name="pod">The pod to remove</param>
private static void RemoveIosDependency(string pod)
{
var dependenciesFilePath = AppLovinDependenciesFilePath;
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath);
if (dependenciesDocument == null) return;
var removed = RemoveDependency(dependenciesDocument,
ElementNameIosPods,
ElementNameIosPod,
AttributeNameName,
pod);
if (!removed) return;
SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
}
public int callbackOrder
{
get { return int.MaxValue; }
get { return CallbackOrder; }
}
}
}
@@ -6,22 +6,21 @@
#if UNITY_ANDROID
using System.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditorInternal;
using AppLovinMax.Internal;
using UnityEngine;
using UnityEngine.Networking;
using Debug = UnityEngine.Debug;
using UnityEngine.PlayerLoop;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
[Serializable]
public class AppLovinQualityServiceData
{
// ReSharper disable once InconsistentNaming - Need to keep name for response data
public string api_key;
}
@@ -57,6 +56,60 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private const string SafeDkLegacyMavenRepo = "http://download.safedk.com";
private const string SafeDkLegacyDependencyClassPath = "com.safedk:SafeDKGradlePlugin:";
/// <summary>
/// Adds the Quality Service plugin to the root gradle file.
/// </summary>
/// <param name="path">The path to the unityLibrary's module.</param>
/// <returns>True if the plugin was added successfully, otherwise return false</returns>
protected static bool AddQualityServiceToRootGradleFile(string path)
{
var rootGradleBuildFilePath = Path.Combine(path, "../build.gradle");
var shouldAddQualityServiceToDependencies = ShouldAddQualityServiceToDependencies(rootGradleBuildFilePath);
if (shouldAddQualityServiceToDependencies)
{
// Add the Quality Service Plugin to the dependencies block in the root build.gradle file
return AddQualityServiceBuildScriptLines(rootGradleBuildFilePath);
}
// Add the Quality Service Plugin to the plugin block in the root build.gradle file
var rootSettingsGradleFilePath = Path.Combine(path, "../settings.gradle");
var qualityServiceAdded = AddPluginToRootGradleBuildFile(rootGradleBuildFilePath);
var appLovinRepositoryAdded = AddAppLovinRepository(rootSettingsGradleFilePath);
return qualityServiceAdded && appLovinRepositoryAdded;
}
/// <summary>
/// Determines whether the AppLovin Quality Service plugin should be added to the
/// dependencies block in the root build.gradle file or to the plugins block.
///
/// Gradle's required structure for including plugins varies by version:
/// - Older versions of Gradle require the plugin to be added to the dependencies block.
/// Example:
/// dependencies {
/// classpath 'com.android.tools.build:gradle:4.0.1'
/// classpath 'com.applovin.quality:AppLovinQualityServiceGradlePlugin:+'
/// }
///
/// - Newer versions of gradle require the plugin to be added to the plugins block.
/// Example:
/// plugins {
/// id 'com.android.application' version '7.4.2' apply false
/// id 'com.android.library' version '7.4.2' apply false
/// id 'com.applovin.quality' version '+' apply false
/// }
///
/// Since Unity projects may use custom Gradle versions depending on the Unity version or
/// user modifications, this check ensures proper integration of the AppLovin plugin.
/// </summary>
/// <param name="rootGradleBuildFile">The path to project's root build.gradle file.</param>
/// <returns><c>true</c> if the file contains a `dependencies` block, indicating an older Gradle version</returns>
private static bool ShouldAddQualityServiceToDependencies(string rootGradleBuildFile)
{
var lines = File.ReadAllLines(rootGradleBuildFile).ToList();
return lines.Any(line => TokenBuildScriptDependencies.IsMatch(line));
}
/// <summary>
/// Updates the provided Gradle script to add Quality Service plugin.
/// </summary>
@@ -87,11 +140,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
var outputLines = GenerateUpdatedBuildFileLines(
sanitizedLines,
apiKey,
#if UNITY_2019_3_OR_NEWER
false // On Unity 2019.3+, the buildscript closure related lines will to be added to the root build.gradle file.
#else
true
#endif
false // The buildscript closure related lines will to be added to the root build.gradle file.
);
// outputLines can be null if we couldn't add the plugin.
if (outputLines == null) return;
@@ -122,7 +171,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
/// <param name="rootGradleBuildFile">The path to project's root build.gradle file.</param>
/// <returns><c>true</c> when the plugin was added successfully.</returns>
protected bool AddPluginToRootGradleBuildFile(string rootGradleBuildFile)
private static bool AddPluginToRootGradleBuildFile(string rootGradleBuildFile)
{
var lines = File.ReadAllLines(rootGradleBuildFile).ToList();
@@ -182,7 +231,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
/// <param name="settingsGradleFile">The path to the project's settings.gradle file.</param>
/// <returns><c>true</c> if the repository was added successfully.</returns>
protected bool AddAppLovinRepository(string settingsGradleFile)
private static bool AddAppLovinRepository(string settingsGradleFile)
{
var lines = File.ReadLines(settingsGradleFile).ToList();
var outputLines = new List<string>();
@@ -244,7 +293,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
return true;
}
#if UNITY_2019_3_OR_NEWER
/// <summary>
/// Adds the necessary AppLovin Quality Service dependency and maven repo lines to the provided root build.gradle file.
/// Sample build.gradle file after adding quality service:
@@ -265,7 +313,7 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
/// <param name="rootGradleBuildFile">The root build.gradle file path</param>
/// <returns><c>true</c> if the build script lines were applied correctly.</returns>
protected bool AddQualityServiceBuildScriptLines(string rootGradleBuildFile)
private static bool AddQualityServiceBuildScriptLines(string rootGradleBuildFile)
{
var lines = File.ReadAllLines(rootGradleBuildFile).ToList();
var outputLines = GenerateUpdatedBuildFileLines(lines, null, true);
@@ -307,40 +355,30 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
Console.WriteLine(exception);
}
}
#endif
private static AppLovinQualityServiceData RetrieveQualityServiceData(string sdkKey)
{
var postJson = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey);
var bodyRaw = Encoding.UTF8.GetBytes(postJson);
// Upload handler is automatically disposed when UnityWebRequest is disposed
var uploadHandler = new UploadHandlerRaw(bodyRaw);
uploadHandler.contentType = "application/json";
using (var unityWebRequest = new UnityWebRequest("https://api2.safedk.com/v1/build/cred"))
var webRequestConfig = new WebRequestConfig()
{
unityWebRequest.method = UnityWebRequest.kHttpVerbPOST;
unityWebRequest.uploadHandler = uploadHandler;
unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
JsonString = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey),
EndPoint = "https://api2.safedk.com/v1/build/cred",
RequestType = WebRequestType.Post,
};
var operation = unityWebRequest.SendWebRequest();
webRequestConfig.Headers.Add("Content-Type", "application/json");
// Wait for the download to complete or the request to timeout.
while (!operation.isDone) { }
var maxWebRequest = new MaxWebRequest(webRequestConfig);
var webResponse = maxWebRequest.SendSync();
#if UNITY_2020_1_OR_NEWER
if (unityWebRequest.result != UnityWebRequest.Result.Success)
#else
if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
#endif
if (!webResponse.IsSuccess)
{
MaxSdkLogger.UserError("Failed to retrieve API Key for SDK Key: " + sdkKey + "with error: " + unityWebRequest.error);
MaxSdkLogger.UserError("Failed to retrieve API Key for SDK Key: " + sdkKey + "with error: " + webResponse.ErrorMessage);
return new AppLovinQualityServiceData();
}
try
{
return JsonUtility.FromJson<AppLovinQualityServiceData>(unityWebRequest.downloadHandler.text);
return JsonUtility.FromJson<AppLovinQualityServiceData>(webResponse.ResponseMessage);
}
catch (Exception exception)
{
@@ -348,7 +386,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
return new AppLovinQualityServiceData();
}
}
}
private static List<string> RemoveLegacySafeDkPlugin(List<string> lines)
{
@@ -413,7 +450,13 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
private static List<string> GenerateUpdatedBuildFileLines(List<string> lines, string apiKey, bool addBuildScriptLines)
{
var addPlugin = !string.IsNullOrEmpty(apiKey);
// Check if the plugin exists, if so, update the SDK Key.
var pluginExists = lines.Any(line => TokenAppLovinPlugin.IsMatch(line));
return pluginExists ? UpdateExistingPlugin(lines, apiKey) : AddPluginAndBuildScript(lines, apiKey, addBuildScriptLines);
}
private static List<string> UpdateExistingPlugin(List<string> lines, string apiKey)
{
// A sample of the template file.
// ...
// allprojects {
@@ -434,10 +477,6 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
// **DEPS**}
// ...
var outputLines = new List<string>();
// Check if the plugin exists, if so, update the SDK Key.
var pluginExists = lines.Any(line => TokenAppLovinPlugin.IsMatch(line));
if (pluginExists)
{
var pluginMatched = false;
var insideAppLovinClosure = false;
var updatedApiKey = false;
@@ -484,24 +523,38 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
outputLines.Add(line);
}
}
return outputLines;
}
// Plugin hasn't been added yet, add it.
else
private static List<string> AddPluginAndBuildScript(List<string> lines, string apiKey, bool addBuildScriptLines)
{
var shouldAddPlugin = MaxSdkUtils.IsValidString(apiKey);
if (shouldAddPlugin)
{
lines = AddPlugin(lines, apiKey);
if (lines == null) return null;
}
if (!addBuildScriptLines) return lines;
lines = AddBuildScript(lines);
return lines;
}
private static List<string> AddBuildScript(List<string> lines)
{
var outputLines = new List<string>();
var buildScriptClosureDepth = 0;
var insideBuildScriptClosure = false;
var buildScriptMatched = false;
var qualityServiceRepositoryAdded = false;
var qualityServiceDependencyClassPathAdded = false;
var qualityServicePluginAdded = false;
foreach (var line in lines)
{
// Add the line to the output lines.
outputLines.Add(line);
// Check if we need to add the build script lines and add them.
if (addBuildScriptLines)
{
if (!buildScriptMatched && line.Contains(BuildScriptMatcher))
{
buildScriptMatched = true;
@@ -547,34 +600,37 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
}
}
// Check if we need to add the plugin and add it.
if (addPlugin)
{
// Add the plugin.
if (!qualityServicePluginAdded && TokenApplicationPlugin.IsMatch(line))
{
outputLines.Add(QualityServiceApplyPlugin);
outputLines.AddRange(GenerateAppLovinPluginClosure(apiKey));
qualityServicePluginAdded = true;
}
}
}
if ((addBuildScriptLines && (!qualityServiceRepositoryAdded || !qualityServiceDependencyClassPathAdded)) || (addPlugin && !qualityServicePluginAdded))
if (!qualityServiceRepositoryAdded || !qualityServiceDependencyClassPathAdded)
{
return null;
}
}
return outputLines;
}
private static List<string> AddPlugin(List<string> lines, string apiKey)
{
var outputLines = new List<string>();
var qualityServicePluginAdded = false;
foreach (var line in lines)
{
outputLines.Add(line);
// Add the plugin.
if (qualityServicePluginAdded || !TokenApplicationPlugin.IsMatch(line)) continue;
outputLines.Add(QualityServiceApplyPlugin);
outputLines.AddRange(GenerateAppLovinPluginClosure(apiKey));
qualityServicePluginAdded = true;
}
return qualityServicePluginAdded ? outputLines : null;
}
public static string GetFormattedBuildScriptLine(string buildScriptLine)
{
#if UNITY_2022_2_OR_NEWER
return " "
#elif UNITY_2019_3_OR_NEWER
return " "
#else
return " "
#endif
@@ -12,17 +12,6 @@ using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
public enum Platform
{
All,
Android,
iOS
}
}
/// <summary>
/// A <see cref="ScriptableObject"/> representing the AppLovin Settings that can be set in the Integration Manager Window.
///
@@ -32,56 +21,19 @@ namespace AppLovinMax.Scripts.IntegrationManager.Editor
/// </summary>
public class AppLovinSettings : ScriptableObject
{
public const string SettingsExportPath = "MaxSdk/Resources/AppLovinSettings.asset";
public const string DefaultUserTrackingDescriptionEnV0 = "Pressing \\\"Allow\\\" uses device info for more relevant ad content";
public const string DefaultUserTrackingDescriptionEnV1 = "This only uses device info for less annoying, more relevant ads";
public const string DefaultUserTrackingDescriptionEnV2 = "This only uses device info for more interesting and relevant ads";
public const string DefaultUserTrackingDescriptionEnV3 = "This uses device info for more personalized ads and content";
public const string DefaultUserTrackingDescriptionDe = "\\\"Erlauben\\\" drücken benutzt Gerätinformationen für relevantere Werbeinhalte";
public const string DefaultUserTrackingDescriptionEs = "Presionando \\\"Permitir\\\", se usa la información del dispositivo para obtener contenido publicitario más relevante";
public const string DefaultUserTrackingDescriptionFr = "\\\"Autoriser\\\" permet d'utiliser les infos du téléphone pour afficher des contenus publicitaires plus pertinents";
public const string DefaultUserTrackingDescriptionJa = "\\\"許可\\\"をクリックすることで、デバイス情報を元により最適な広告を表示することができます";
public const string DefaultUserTrackingDescriptionKo = "\\\"허용\\\"을 누르면 더 관련성 높은 광고 콘텐츠를 제공하기 위해 기기 정보가 사용됩니다";
public const string DefaultUserTrackingDescriptionZhHans = "点击\\\"允许\\\"以使用设备信息获得更加相关的广告内容";
public const string DefaultUserTrackingDescriptionZhHant = "點擊\\\"允許\\\"以使用設備信息獲得更加相關的廣告內容";
/// <summary>
/// A placeholder constant to be replaced with the actual default localization or an empty string based on whether or not localization is enabled when when the getter is called.
/// </summary>
protected const string DefaultLocalization = "default_localization";
private const string SettingsExportPath = "MaxSdk/Resources/AppLovinSettings.asset";
private static AppLovinSettings instance;
[SerializeField] private bool qualityServiceEnabled = true;
[SerializeField] private string sdkKey;
[SerializeField] private bool setAttributionReportEndpoint;
[SerializeField] private bool addApsSkAdNetworkIds;
[SerializeField] private string customGradleVersionUrl;
[SerializeField] private string customGradleToolsVersion;
[SerializeField] private bool consentFlowEnabled;
[SerializeField] private Platform consentFlowPlatform;
[SerializeField] private string consentFlowPrivacyPolicyUrl = string.Empty;
[SerializeField] private string consentFlowTermsOfServiceUrl = string.Empty;
[FormerlySerializedAs("userTrackingUsageDescription")] [SerializeField] private string userTrackingUsageDescriptionEn = string.Empty;
[SerializeField] private bool userTrackingUsageLocalizationEnabled;
[SerializeField] private string userTrackingUsageDescriptionDe = string.Empty;
[SerializeField] private string userTrackingUsageDescriptionEs = string.Empty;
[SerializeField] private string userTrackingUsageDescriptionFr = string.Empty;
[SerializeField] private string userTrackingUsageDescriptionJa = string.Empty;
[SerializeField] private string userTrackingUsageDescriptionKo = string.Empty;
[SerializeField] private string userTrackingUsageDescriptionZhHans = string.Empty;
[SerializeField] private string userTrackingUsageDescriptionZhHant = DefaultLocalization;
[SerializeField] private string adMobAndroidAppId = string.Empty;
[SerializeField] private string adMobIosAppId = string.Empty;
[SerializeField] private bool showInternalSettingsInIntegrationManager;
/// <summary>
/// An instance of AppLovin Setting.
/// </summary>
@@ -105,16 +57,15 @@ public class AppLovinSettings : ScriptableObject
return instance;
}
// If there is no existing AppLovinSettings asset, create one in the default location
string settingsFilePath;
// The settings file should be under the Assets/ folder so that it can be version controlled and cannot be overriden when updating.
// If the plugin is outside the Assets folder, create the settings asset at the default location.
if (AppLovinIntegrationManager.IsPluginOutsideAssetsDirectory)
// If the plugin is outside the Assets folder or if there is no existing AppLovinSettings asset, create the settings asset at the default location.
if (AppLovinIntegrationManager.IsPluginInPackageManager)
{
// Note: Can't use absolute path when calling `CreateAsset`. Should use relative path to Assets/ directory.
settingsFilePath = Path.Combine("Assets", SettingsExportPath);
// Note: Can't use absolute path when calling `CreateAsset`. Should use path relative to Assets/ directory.
settingsFilePath = MaxSdkUtils.NormalizeToUnityPath(Path.Combine("Assets", SettingsExportPath));
var maxSdkDir = Path.Combine(Application.dataPath, "MaxSdk");
var maxSdkDir = MaxSdkUtils.NormalizeToUnityPath(Path.Combine(Application.dataPath, "MaxSdk"));
if (!Directory.Exists(maxSdkDir))
{
Directory.CreateDirectory(maxSdkDir);
@@ -122,7 +73,7 @@ public class AppLovinSettings : ScriptableObject
}
else
{
settingsFilePath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, SettingsExportPath);
settingsFilePath = MaxSdkUtils.NormalizeToUnityPath(Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, SettingsExportPath));
}
var settingsDir = Path.GetDirectoryName(settingsFilePath);
@@ -162,24 +113,6 @@ public class AppLovinSettings : ScriptableObject
set { Instance.sdkKey = value; }
}
/// <summary>
/// Whether or not to set `NSAdvertisingAttributionReportEndpoint` in Info.plist.
/// </summary>
public bool SetAttributionReportEndpoint
{
get { return Instance.setAttributionReportEndpoint; }
set { Instance.setAttributionReportEndpoint = value; }
}
/// <summary>
/// Whether or not to add Amazon Publisher Services SKAdNetworkID's.
/// </summary>
public bool AddApsSkAdNetworkIds
{
get { return Instance.addApsSkAdNetworkIds; }
set { Instance.addApsSkAdNetworkIds = value; }
}
/// <summary>
/// A URL to set the distributionUrl in the gradle-wrapper.properties file (ex: https\://services.gradle.org/distributions/gradle-6.9.3-bin.zip)
/// </summary>
@@ -198,199 +131,6 @@ public class AppLovinSettings : ScriptableObject
set { Instance.customGradleToolsVersion = value; }
}
/// <summary>
/// Whether or not AppLovin Consent Flow is enabled.
/// </summary>
public bool ConsentFlowEnabled
{
get
{
// Update the default EN description if an old version of the description is still being used.
if (DefaultUserTrackingDescriptionEnV0.Equals(Instance.UserTrackingUsageDescriptionEn)
|| DefaultUserTrackingDescriptionEnV1.Equals(Instance.UserTrackingUsageDescriptionEn)
|| DefaultUserTrackingDescriptionEnV2.Equals(Instance.UserTrackingUsageDescriptionEn))
{
Instance.UserTrackingUsageDescriptionEn = DefaultUserTrackingDescriptionEnV3;
}
return Instance.consentFlowEnabled;
}
set
{
var previousValue = Instance.consentFlowEnabled;
Instance.consentFlowEnabled = value;
if (value)
{
// If the value didn't change, we don't need to update anything.
if (previousValue) return;
Instance.UserTrackingUsageDescriptionEn = DefaultUserTrackingDescriptionEnV3;
Instance.UserTrackingUsageLocalizationEnabled = true;
}
else
{
Instance.ConsentFlowPlatform = Platform.All;
Instance.ConsentFlowPrivacyPolicyUrl = string.Empty;
Instance.ConsentFlowTermsOfServiceUrl = string.Empty;
Instance.UserTrackingUsageDescriptionEn = string.Empty;
Instance.UserTrackingUsageLocalizationEnabled = false;
}
}
}
public Platform ConsentFlowPlatform
{
get { return Instance.consentFlowEnabled ? Instance.consentFlowPlatform : Platform.All; }
set { Instance.consentFlowPlatform = value; }
}
/// <summary>
/// A URL pointing to the Privacy Policy for the app to be shown when prompting the user for consent.
/// </summary>
public string ConsentFlowPrivacyPolicyUrl
{
get { return Instance.consentFlowPrivacyPolicyUrl; }
set { Instance.consentFlowPrivacyPolicyUrl = value; }
}
/// <summary>
/// An optional URL pointing to the Terms of Service for the app to be shown when prompting the user for consent.
/// </summary>
public string ConsentFlowTermsOfServiceUrl
{
get { return Instance.consentFlowTermsOfServiceUrl; }
set { Instance.consentFlowTermsOfServiceUrl = value; }
}
/// <summary>
/// A User Tracking Usage Description in English to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionEn
{
get { return Instance.userTrackingUsageDescriptionEn; }
set { Instance.userTrackingUsageDescriptionEn = value; }
}
/// <summary>
/// Whether or not to localize User Tracking Usage Description.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public bool UserTrackingUsageLocalizationEnabled
{
get { return Instance.userTrackingUsageLocalizationEnabled; }
set
{
var previousValue = Instance.userTrackingUsageLocalizationEnabled;
Instance.userTrackingUsageLocalizationEnabled = value;
if (value)
{
// If the value didn't change or the english localization text is not the default one, we don't need to update anything.
if (previousValue || !DefaultUserTrackingDescriptionEnV3.Equals(Instance.UserTrackingUsageDescriptionEn)) return;
Instance.UserTrackingUsageDescriptionDe = DefaultUserTrackingDescriptionDe;
Instance.UserTrackingUsageDescriptionEs = DefaultUserTrackingDescriptionEs;
Instance.UserTrackingUsageDescriptionFr = DefaultUserTrackingDescriptionFr;
Instance.UserTrackingUsageDescriptionJa = DefaultUserTrackingDescriptionJa;
Instance.UserTrackingUsageDescriptionKo = DefaultUserTrackingDescriptionKo;
Instance.UserTrackingUsageDescriptionZhHans = DefaultUserTrackingDescriptionZhHans;
Instance.UserTrackingUsageDescriptionZhHant = DefaultUserTrackingDescriptionZhHant;
}
else
{
Instance.UserTrackingUsageDescriptionDe = string.Empty;
Instance.UserTrackingUsageDescriptionEs = string.Empty;
Instance.UserTrackingUsageDescriptionFr = string.Empty;
Instance.UserTrackingUsageDescriptionJa = string.Empty;
Instance.UserTrackingUsageDescriptionKo = string.Empty;
Instance.UserTrackingUsageDescriptionZhHans = string.Empty;
Instance.UserTrackingUsageDescriptionZhHant = string.Empty;
}
}
}
/// <summary>
/// A User Tracking Usage Description in German to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionDe
{
get { return Instance.userTrackingUsageDescriptionDe; }
set { Instance.userTrackingUsageDescriptionDe = value; }
}
/// <summary>
/// A User Tracking Usage Description in Spanish to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionEs
{
get { return Instance.userTrackingUsageDescriptionEs; }
set { Instance.userTrackingUsageDescriptionEs = value; }
}
/// <summary>
/// A User Tracking Usage Description in French to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionFr
{
get { return Instance.userTrackingUsageDescriptionFr; }
set { Instance.userTrackingUsageDescriptionFr = value; }
}
/// <summary>
/// A User Tracking Usage Description in Japanese to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionJa
{
get { return Instance.userTrackingUsageDescriptionJa; }
set { Instance.userTrackingUsageDescriptionJa = value; }
}
/// <summary>
/// A User Tracking Usage Description in Korean to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionKo
{
get { return Instance.userTrackingUsageDescriptionKo; }
set { Instance.userTrackingUsageDescriptionKo = value; }
}
/// <summary>
/// A User Tracking Usage Description in Chinese (Simplified) to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionZhHans
{
get { return Instance.userTrackingUsageDescriptionZhHans; }
set { Instance.userTrackingUsageDescriptionZhHans = value; }
}
/// <summary>
/// A User Tracking Usage Description in Chinese (Traditional) to be shown to users when requesting permission to use data for tracking.
/// For more information see <see cref="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
/// </summary>
public string UserTrackingUsageDescriptionZhHant
{
get
{
// Since this localization has been added separate from the other localizations,
// we use a placeholder constant to be replaced with the actual value or an empty string based on whether or not the localization was enabled by the publisher.
if (DefaultLocalization.Equals(Instance.userTrackingUsageDescriptionZhHant))
{
Instance.userTrackingUsageDescriptionZhHant = Instance.UserTrackingUsageLocalizationEnabled ? DefaultUserTrackingDescriptionZhHant : string.Empty;
}
return Instance.userTrackingUsageDescriptionZhHant;
}
set { Instance.userTrackingUsageDescriptionZhHant = value; }
}
/// <summary>
/// AdMob Android App ID.
/// </summary>
@@ -409,12 +149,6 @@ public class AppLovinSettings : ScriptableObject
set { Instance.adMobIosAppId = value; }
}
public bool ShowInternalSettingsInIntegrationManager
{
get { return Instance.showInternalSettingsInIntegrationManager; }
set { Instance.showInternalSettingsInIntegrationManager = value; }
}
/// <summary>
/// Saves the instance of the settings.
/// </summary>
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AppLovinMax.ThirdParty.MiniJson;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
public class AppLovinUpmManifest
{
private const string KeyUrl = "url";
private const string KeyName = "name";
private const string KeyScopes = "scopes";
private const string KeyScopedRegistry = "scopedRegistries";
private Dictionary<string, object> manifest;
private static string ManifestPath
{
get { return Path.Combine(Directory.GetCurrentDirectory(), "Packages/manifest.json"); }
}
// Private constructor to enforce the use of the Load() method
private AppLovinUpmManifest() { }
/// <summary>
/// Creates a new instance of AppLovinUpmManifest and loads the manifest.json file.
/// </summary>
/// <returns>An instance of AppLovinUpmManifest</returns>
public static AppLovinUpmManifest Load()
{
return new AppLovinUpmManifest { manifest = GetManifest() };
}
/// <summary>
/// Adds or updates a scoped registry in the manifest.
/// </summary>
/// <param name="name">The name of the registry</param>
/// <param name="url">The url of the registry</param>
/// <param name="scopes">The scopes of the registry</param>
public void AddOrUpdateRegistry(string name, string url, List<string> scopes)
{
var registry = GetRegistry(name);
if (registry == null)
{
var registries = GetRegistries();
if (registries == null) return;
registries.Add(new Dictionary<string, object>
{
{KeyName, name},
{KeyUrl, url},
{KeyScopes, scopes}
});
return;
}
UpdateRegistry(registry, scopes);
}
/// <summary>
/// Saves the manifest by serializing it back to JSON and writing to file.
/// </summary>
public void Save()
{
var content = Json.Serialize(manifest, true);
File.WriteAllText(ManifestPath, content);
}
/// <summary>
/// Adds a dependency to the manifest.
/// </summary>
/// <param name="packageName">The name of the package to add</param>
/// <param name="version">The version of the package to add</param>
public void AddPackageDependency(string packageName, string version)
{
var manifestDependencies = GetDependencies();
manifestDependencies[packageName] = version;
}
/// <summary>
/// Removes a dependency from the manifest.
/// </summary>
/// <param name="packageName">The name of the package to remove</param>
public void RemovePackageDependency(string packageName)
{
var manifestDependencies = GetDependencies();
manifestDependencies.Remove(packageName);
}
#region Utility
/// <summary>
/// Returns the manifest.json file as a dictionary.
/// </summary>
private static Dictionary<string, object> GetManifest()
{
if (!File.Exists(ManifestPath))
{
throw new Exception("Manifest not Found!");
}
var manifestJson = File.ReadAllText(ManifestPath);
if (string.IsNullOrEmpty(manifestJson))
{
throw new Exception("Manifest is empty!");
}
var deserializedManifest = Json.Deserialize(manifestJson) as Dictionary<string, object>;
if (deserializedManifest == null)
{
throw new Exception("Failed to deserialize manifest");
}
return deserializedManifest;
}
/// <summary>
/// Gets the manifest's dependencies section.
/// </summary>
/// <returns>The dependencies section of the manifest.</returns>
private Dictionary<string, object> GetDependencies()
{
var dependencies = manifest["dependencies"] as Dictionary<string, object>;
if (dependencies == null)
{
throw new Exception("No dependencies found in manifest.");
}
return dependencies;
}
/// <summary>
/// Gets the manifest's registries section. Creates a new registries section if one does not exist.
/// </summary>
/// <returns>The registries section of the manifest.</returns>
private List<object> GetRegistries()
{
EnsureScopedRegistryExists();
return manifest[KeyScopedRegistry] as List<object>;
}
/// <summary>
/// Gets a scoped registry with the given name.
/// </summary>
/// <param name="name">The name of the registry</param>
/// <returns>Returns the registry, or null if it can't be found</returns>
private Dictionary<string, object> GetRegistry(string name)
{
var registries = GetRegistries();
if (registries == null) return null;
return registries
.OfType<Dictionary<string, object>>()
.FirstOrDefault(registry => MaxSdkUtils.GetStringFromDictionary(registry, KeyName).Equals(name));
}
/// <summary>
/// Creates the section for scoped registries in the manifest.json file if it doesn't exist.
/// </summary>
private void EnsureScopedRegistryExists()
{
if (manifest.ContainsKey(KeyScopedRegistry)) return;
manifest.Add(KeyScopedRegistry, new List<object>());
}
/// <summary>
/// Updates a registry to make sure it contains the new scopes.
/// </summary>
/// <param name="registry">The registry to update</param>
/// <param name="newScopes">The scopes we want added to the registry</param>
private static void UpdateRegistry(Dictionary<string, object> registry, List<string> newScopes)
{
var scopes = MaxSdkUtils.GetListFromDictionary(registry, KeyScopes);
if (scopes == null)
{
registry[KeyScopes] = new List<string>(newScopes);
return;
}
// Only add scopes that are not already in the list
var uniqueNewScopes = newScopes.Where(scope => !scopes.Contains(scope)).ToList();
scopes.AddRange(uniqueNewScopes);
}
#endregion
}
}
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 40e2ad4a252104688b8627f82e806b2e
labels:
- al_max
- al_max_export_path-MaxSdk/Scripts/AppLovinUpmManifest.cs
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinUpmManifest.cs
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+4 -4
View File
@@ -25,7 +25,7 @@ public class MaxCmpService
private MaxCmpService() { }
private static Action<MaxCmpError> OnCompletedAction;
private static Action<MaxCmpError> _onCompletedAction;
#if UNITY_EDITOR
#elif UNITY_ANDROID
@@ -50,7 +50,7 @@ public class MaxCmpService
/// <param name="onCompletedAction">Called when the CMP flow finishes showing.</param>
public void ShowCmpForExistingUser(Action<MaxCmpError> onCompletedAction)
{
OnCompletedAction = onCompletedAction;
_onCompletedAction = onCompletedAction;
#if UNITY_EDITOR
var errorProps = new Dictionary<string, object>
@@ -88,10 +88,10 @@ public class MaxCmpService
internal static void NotifyCompletedIfNeeded(Dictionary<string, object> errorProps)
{
if (OnCompletedAction == null) return;
if (_onCompletedAction == null) return;
var error = (errorProps == null) ? null : MaxCmpError.Create(errorProps);
OnCompletedAction(error);
_onCompletedAction(error);
}
}
+23 -23
View File
@@ -15,31 +15,31 @@ namespace AppLovinMax.Internal
{
public class MaxEventExecutor : MonoBehaviour
{
private static MaxEventExecutor instance;
private static List<MaxAction> adEventsQueue = new List<MaxAction>();
private static MaxEventExecutor _instance;
private static readonly List<MaxAction> AdEventsQueue = new List<MaxAction>();
private static volatile bool adEventsQueueEmpty = true;
private static volatile bool _adEventsQueueEmpty = true;
struct MaxAction
{
public Action action;
public string eventName;
public readonly Action ActionToExecute;
public readonly string EventName;
public MaxAction(Action actionToExecute, string nameOfEvent)
{
action = actionToExecute;
eventName = nameOfEvent;
ActionToExecute = actionToExecute;
EventName = nameOfEvent;
}
}
public static void InitializeIfNeeded()
{
if (instance != null) return;
if (_instance != null) return;
var executor = new GameObject("MaxEventExecutor");
executor.hideFlags = HideFlags.HideAndDontSave;
DontDestroyOnLoad(executor);
instance = executor.AddComponent<MaxEventExecutor>();
_instance = executor.AddComponent<MaxEventExecutor>();
}
#region Public API
@@ -50,17 +50,17 @@ namespace AppLovinMax.Internal
get
{
InitializeIfNeeded();
return instance;
return _instance;
}
}
#endif
public static void ExecuteOnMainThread(Action action, string eventName)
{
lock (adEventsQueue)
lock (AdEventsQueue)
{
adEventsQueue.Add(new MaxAction(action, eventName));
adEventsQueueEmpty = false;
AdEventsQueue.Add(new MaxAction(action, eventName));
_adEventsQueueEmpty = false;
}
}
@@ -73,28 +73,28 @@ namespace AppLovinMax.Internal
public void Update()
{
if (adEventsQueueEmpty) return;
if (_adEventsQueueEmpty) return;
var actionsToExecute = new List<MaxAction>();
lock (adEventsQueue)
lock (AdEventsQueue)
{
actionsToExecute.AddRange(adEventsQueue);
adEventsQueue.Clear();
adEventsQueueEmpty = true;
actionsToExecute.AddRange(AdEventsQueue);
AdEventsQueue.Clear();
_adEventsQueueEmpty = true;
}
foreach (var maxAction in actionsToExecute)
{
if (maxAction.action.Target != null)
if (maxAction.ActionToExecute.Target != null)
{
try
{
maxAction.action.Invoke();
maxAction.ActionToExecute.Invoke();
}
catch (Exception exception)
{
MaxSdkLogger.UserError("Caught exception in publisher event: " + maxAction.eventName + ", exception: " + exception);
Debug.LogException(exception);
MaxSdkLogger.UserError("Caught exception in publisher event: " + maxAction.EventName + ", exception: " + exception);
MaxSdkLogger.LogException(exception);
}
}
}
@@ -102,7 +102,7 @@ namespace AppLovinMax.Internal
public void Disable()
{
instance = null;
_instance = null;
}
}
}
@@ -22,7 +22,7 @@ namespace AppLovinMax.Scripts
{
// Enable the EventSystem if there is no other EventSystem in the scene
var eventSystem = GetComponent<EventSystem>();
var currentSystem = UnityEngine.EventSystems.EventSystem.current;
var currentSystem = EventSystem.current;
if (currentSystem == null || currentSystem == eventSystem)
{
eventSystem.enabled = true;
+4 -8
View File
@@ -1,10 +1,6 @@
/**
* AppLovin MAX Unity Plugin C# Wrapper
*/
using UnityEngine;
using System.Collections.Generic;
using System.Runtime.InteropServices;
//
// AppLovin MAX Unity Plugin C# Wrapper
//
public class MaxSdk :
#if UNITY_EDITOR
@@ -18,7 +14,7 @@ public class MaxSdk :
MaxSdkUnityEditor
#endif
{
private const string _version = "7.0.0";
private const string _version = "8.6.3";
/// <summary>
/// Returns the current plugin version.
+62 -145
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using UnityEngine;
using AppLovinMax.ThirdParty.MiniJson;
#if UNITY_ANDROID
/// <summary>
/// Android AppLovin MAX Unity Plugin implementation
/// </summary>
@@ -11,12 +12,7 @@ public class MaxSdkAndroid : MaxSdkBase
private static readonly AndroidJavaClass MaxUnityPluginClass =
new AndroidJavaClass("com.applovin.mediation.unity.MaxUnityPlugin");
private static BackgroundCallbackProxy BackgroundCallback = new BackgroundCallbackProxy();
public static MaxUserServiceAndroid UserService
{
get { return MaxUserServiceAndroid.Instance; }
}
private static readonly BackgroundCallbackProxy BackgroundCallback = new BackgroundCallbackProxy();
static MaxSdkAndroid()
{
@@ -85,10 +81,10 @@ public class MaxSdkAndroid : MaxSdkBase
///
/// Please call this method after the SDK has initialized.
/// </summary>
public static List<MaxSdkBase.MediatedNetworkInfo> GetAvailableMediatedNetworks()
public static List<MediatedNetworkInfo> GetAvailableMediatedNetworks()
{
var serializedNetworks = MaxUnityPluginClass.CallStatic<string>("getAvailableMediatedNetworks");
return MaxSdkUtils.PropsStringsToList<MaxSdkBase.MediatedNetworkInfo>(serializedNetworks);
return MaxSdkUtils.PropsStringsToList<MediatedNetworkInfo>(serializedNetworks);
}
/// <summary>
@@ -156,7 +152,7 @@ public class MaxSdkAndroid : MaxSdkBase
/// <summary>
/// Check if user has provided consent for information sharing with AppLovin and other providers.
/// </summary>
/// <returns><c>true</c> if user has provided consent for information sharing. <c>false</c> if the user declined to share information or the consent value has not been set <see cref="IsUserConsentSet">.</returns>
/// <returns><c>true</c> if user has provided consent for information sharing. <c>false</c> if the user declined to share information or the consent value has not been set. See <see cref="IsUserConsentSet">IsUserConsentSet</see>.</returns>
public static bool HasUserConsent()
{
return MaxUnityPluginClass.CallStatic<bool>("hasUserConsent");
@@ -183,7 +179,7 @@ public class MaxSdkAndroid : MaxSdkBase
/// <summary>
/// Check if the user has opted out of the sale of their personal information.
/// </summary>
/// <returns><c>true</c> if the user has opted out of the sale of their personal information. <c>false</c> if the user opted in to the sell of their personal information or the value has not been set <see cref="IsDoNotSellSet">.</returns>
/// <returns><c>true</c> if the user has opted out of the sale of their personal information. <c>false</c> if the user opted in to the sell of their personal information or the value has not been set. See <see cref="IsDoNotSellSet">IsDoNotSellSet</see>.</returns>
public static bool IsDoNotSell()
{
return MaxUnityPluginClass.CallStatic<bool>("isDoNotSell");
@@ -206,26 +202,18 @@ public class MaxSdkAndroid : MaxSdkBase
/// Create a new banner.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
/// <param name="bannerPosition">Banner position. Must not be null.</param>
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
/// <param name="configuration">The configuration for the banner</param>
public static void CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
MaxUnityPluginClass.CallStatic("createBanner", adUnitIdentifier, bannerPosition.ToSnakeCaseString());
if (configuration.UseCoordinates)
{
MaxUnityPluginClass.CallStatic("createBanner", adUnitIdentifier, configuration.XCoordinate, configuration.YCoordinate, configuration.IsAdaptive);
}
/// <summary>
/// Create a new banner with a custom position.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
/// <param name="x">The X coordinate (horizontal position) of the banner relative to the top left corner of the screen.</param>
/// <param name="y">The Y coordinate (vertical position) of the banner relative to the top left corner of the screen.</param>
/// <seealso cref="GetBannerLayout">
/// The banner is placed within the safe area of the screen. You can use this to get the absolute position of the banner on screen.
/// </seealso>
public static void CreateBanner(string adUnitIdentifier, float x, float y)
else
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
MaxUnityPluginClass.CallStatic("createBanner", adUnitIdentifier, x, y);
MaxUnityPluginClass.CallStatic("createBanner", adUnitIdentifier, configuration.Position.ToSnakeCaseString(), configuration.IsAdaptive);
}
}
/// <summary>
@@ -276,7 +264,7 @@ public class MaxSdkAndroid : MaxSdkBase
/// </summary>
/// <param name="adUnitIdentifier">The ad unit identifier of the banner for which to update the position. Must not be null.</param>
/// <param name="bannerPosition">A new position for the banner. Must not be null.</param>
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
public static void UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "update banner position");
MaxUnityPluginClass.CallStatic("updateBannerPosition", adUnitIdentifier, bannerPosition.ToSnakeCaseString());
@@ -413,26 +401,18 @@ public class MaxSdkAndroid : MaxSdkBase
/// Create a new MREC.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
/// <param name="mrecPosition">MREC position. Must not be null.</param>
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
/// <param name="configuration">The configuration for the MREC.</param>
public static void CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
MaxUnityPluginClass.CallStatic("createMRec", adUnitIdentifier, mrecPosition.ToSnakeCaseString());
if (configuration.UseCoordinates)
{
MaxUnityPluginClass.CallStatic("createMRec", adUnitIdentifier, configuration.XCoordinate, configuration.YCoordinate);
}
/// <summary>
/// Create a new MREC with a custom position.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
/// <param name="x">The X coordinate (horizontal position) of the MREC relative to the top left corner of the screen.</param>
/// <param name="y">The Y coordinate (vertical position) of the MREC relative to the top left corner of the screen.</param>
/// <seealso cref="GetMRecLayout">
/// The MREC is placed within the safe area of the screen. You can use this to get the absolute position Rect of the MREC on screen.
/// </seealso>
public static void CreateMRec(string adUnitIdentifier, float x, float y)
else
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
MaxUnityPluginClass.CallStatic("createMRec", adUnitIdentifier, x, y);
MaxUnityPluginClass.CallStatic("createMRec", adUnitIdentifier, configuration.Position.ToSnakeCaseString());
}
}
/// <summary>
@@ -823,83 +803,6 @@ public class MaxSdkAndroid : MaxSdkBase
#endregion
#region Rewarded Interstitial
/// <summary>
/// Start loading an rewarded interstitial ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to load. Must not be null.</param>
public static void LoadRewardedInterstitialAd(string adUnitIdentifier)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "load rewarded interstitial ad");
MaxUnityPluginClass.CallStatic("loadRewardedInterstitialAd", adUnitIdentifier);
}
/// <summary>
/// Check if rewarded interstitial ad ad is loaded and ready to be displayed.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to load. Must not be null.</param>
/// <returns>True if the ad is ready to be displayed</returns>
public static bool IsRewardedInterstitialAdReady(string adUnitIdentifier)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "check rewarded interstitial ad loaded");
return MaxUnityPluginClass.CallStatic<bool>("isRewardedInterstitialAdReady", adUnitIdentifier);
}
/// <summary>
/// Present loaded rewarded interstitial ad for a given placement to tie ad events to. Note: if the rewarded interstitial ad is not ready to be displayed nothing will happen.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial to show. Must not be null.</param>
/// <param name="placement">The placement to tie the showing ad's events to</param>
/// <param name="customData">The custom data to tie the showing ad's events to. Maximum size is 8KB.</param>
public static void ShowRewardedInterstitialAd(string adUnitIdentifier, string placement = null, string customData = null)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "show rewarded interstitial ad");
if (IsRewardedInterstitialAdReady(adUnitIdentifier))
{
MaxUnityPluginClass.CallStatic("showRewardedInterstitialAd", adUnitIdentifier, placement, customData);
}
else
{
MaxSdkLogger.UserWarning("Not showing MAX Ads rewarded interstitial ad: ad not ready");
}
}
/// <summary>
/// Set an extra parameter for the ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial to set the extra parameter for. Must not be null.</param>
/// <param name="key">The key for the extra parameter. Must not be null.</param>
/// <param name="value">The value for the extra parameter.</param>
public static void SetRewardedInterstitialAdExtraParameter(string adUnitIdentifier, string key, string value)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded interstitial ad extra parameter");
MaxUnityPluginClass.CallStatic("setRewardedInterstitialAdExtraParameter", adUnitIdentifier, key, value);
}
/// <summary>
/// Set a local extra parameter for the ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial to set the local extra parameter for. Must not be null.</param>
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
/// <param name="value">The value for the extra parameter. Accepts the following types: <see cref="AndroidJavaObject"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
public static void SetRewardedInterstitialAdLocalExtraParameter(string adUnitIdentifier, string key, object value)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded interstitial ad local extra parameter");
if (value == null || value is AndroidJavaObject)
{
MaxUnityPluginClass.CallStatic("setRewardedInterstitialAdLocalExtraParameter", adUnitIdentifier, key, (AndroidJavaObject) value);
}
else
{
MaxUnityPluginClass.CallStatic("setRewardedInterstitialAdLocalExtraParameterJson", adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
}
}
#endregion
#region Event Tracking
/// <summary>
@@ -909,7 +812,9 @@ public class MaxSdkAndroid : MaxSdkBase
/// <param name="parameters">A dictionary containing key-value pairs further describing this event.</param>
public static void TrackEvent(string name, IDictionary<string, string> parameters = null)
{
MaxUnityPluginClass.CallStatic("trackEvent", name, Json.Serialize(parameters));
// Convert null to "{}" to avoid Unity sending the literal "null" to Android.
var jsonString = ( parameters == null ) ? EmptyJson : Json.Serialize(parameters);
MaxUnityPluginClass.CallStatic("trackEvent", name, jsonString);
}
#endregion
@@ -999,6 +904,8 @@ public class MaxSdkAndroid : MaxSdkBase
/// <param name="value">The value for the extra parameter. May be null.</param>
public static void SetExtraParameter(string key, string value)
{
HandleExtraParameter(key, value);
MaxUnityPluginClass.CallStatic("setExtraParameter", key, value);
}
@@ -1025,34 +932,43 @@ public class MaxSdkAndroid : MaxSdkBase
#region Obsolete
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
{
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
CreateBanner(adUnitIdentifier, new AdViewConfiguration((AdViewPosition) bannerPosition));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateBanner(string adUnitIdentifier, float x, float y)
{
CreateBanner(adUnitIdentifier, new AdViewConfiguration(x, y));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition) instead.")]
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
{
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
UpdateBannerPosition(adUnitIdentifier, (AdViewPosition) bannerPosition);
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
{
CreateMRec(adUnitIdentifier, new AdViewConfiguration(mrecPosition));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateMRec(string adUnitIdentifier, float x, float y)
{
CreateMRec(adUnitIdentifier, new AdViewConfiguration(x, y));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.")]
public static void SetSdkKey(string sdkKey)
{
MaxUnityPluginClass.CallStatic("setSdkKey", sdkKey);
Debug.LogWarning("MaxSdk.SetSdkKey() has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.");
}
[Obsolete("This method has been deprecated. Please use `GetSdkConfiguration().ConsentDialogState`")]
public static ConsentDialogState GetConsentDialogState()
{
if (!IsInitialized())
{
MaxSdkLogger.UserWarning(
"MAX Ads SDK has not been initialized yet. GetConsentDialogState() may return ConsentDialogState.Unknown");
}
return (ConsentDialogState) MaxUnityPluginClass.CallStatic<int>("getConsentDialogState");
}
[Obsolete("This method has been deprecated. The AdInfo object is returned with ad callbacks.")]
public static AdInfo GetAdInfo(string adUnitIdentifier)
{
var adInfoString = MaxUnityPluginClass.CallStatic<string>("getAdInfo", adUnitIdentifier);
if (string.IsNullOrEmpty(adInfoString)) return null;
var adInfoDictionary = Json.Deserialize(adInfoString) as Dictionary<string, object>;
return new AdInfo(adInfoDictionary);
MaxSdkLogger.UserWarning("MaxSdk.SetSdkKey() has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.");
}
#endregion
@@ -1067,3 +983,4 @@ public class MaxSdkAndroid : MaxSdkBase
}
}
}
#endif
+152 -125
View File
@@ -14,6 +14,8 @@ using System.Runtime.InteropServices;
public abstract class MaxSdkBase
{
protected const string EmptyJson = "{}";
/// <summary>
/// This enum represents the user's geography used to determine the type of consent flow shown to the user.
/// </summary>
@@ -42,7 +44,7 @@ public abstract class MaxSdkBase
public enum AppTrackingStatus
{
/// <summary>
/// Device is on < iOS14, AppTrackingTransparency.framework is not available.
/// Device is on &lt; iOS14, AppTrackingTransparency.framework is not available.
/// </summary>
Unavailable,
@@ -68,6 +70,42 @@ public abstract class MaxSdkBase
}
#endif
/// <summary>
/// An enum describing the adapter's initialization status.
/// </summary>
public enum InitializationStatus
{
/// <summary>
/// The adapter is not initialized. Note: networks need to be enabled for an ad unit id to be initialized.
/// </summary>
NotInitialized = -4,
/// <summary>
/// The 3rd-party SDK does not have an initialization callback with status.
/// </summary>
DoesNotApply = -3,
/// <summary>
/// The 3rd-party SDK is currently initializing.
/// </summary>
Initializing = -2,
/// <summary>
/// The 3rd-party SDK explicitly initialized, but without a status.
/// </summary>
InitializedUnknown = -1,
/// <summary>
/// The 3rd-party SDK initialization failed.
/// </summary>
InitializedFailure = 0,
/// <summary>
/// The 3rd-party SDK initialization was successful.
/// </summary>
InitializedSuccess = 1
}
public enum AdViewPosition
{
TopLeft,
@@ -81,17 +119,53 @@ public abstract class MaxSdkBase
BottomRight
}
public enum BannerPosition
public class AdViewConfiguration
{
TopLeft,
TopCenter,
TopRight,
Centered,
CenterLeft,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight
/// <summary>
/// The position of the ad.
/// </summary>
public AdViewPosition Position { get; private set; }
/// <summary>
/// The horizontal (X) position of the banner, relative to the top-left corner of the screen's safe area.
/// </summary>
public float XCoordinate { get; private set; }
/// <summary>
/// The vertical (Y) position of the banner, relative to the top-left corner of the screen's safe area.
/// </summary>
public float YCoordinate { get; private set; }
/// <summary>
/// Whether to use adaptive banners. Has no effect on MREC ads.
/// </summary>
public bool IsAdaptive { get; set; }
internal bool UseCoordinates { get; private set; }
/// <summary>
/// Creates an AdViewConfiguration with the given AdViewPosition.
/// </summary>
/// <param name="position">The position of the ad. Must not be null.</param>
public AdViewConfiguration(AdViewPosition position)
{
Position = position;
IsAdaptive = true;
UseCoordinates = false;
}
/// <summary>
/// Creates an AdViewConfiguration with the given x and y coordinates.
/// </summary>
/// <param name="x">The horizontal (X) position of the banner, relative to the top-left corner of the screen's safe area.</param>
/// <param name="y">The vertical (Y) position of the banner, relative to the top-left corner of the screen's safe area.</param>
public AdViewConfiguration(float x, float y)
{
XCoordinate = x;
YCoordinate = y;
IsAdaptive = true;
UseCoordinates = true;
}
}
public class SdkConfiguration
@@ -135,12 +209,27 @@ public abstract class MaxSdkBase
#if UNITY_EDITOR
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.Authorized;
#endif
var currentRegion = RegionInfo.CurrentRegion;
sdkConfiguration.CountryCode = currentRegion != null ? currentRegion.TwoLetterISORegionName : "US";
sdkConfiguration.CountryCode = TryGetCountryCode();
sdkConfiguration.IsTestModeEnabled = false;
return sdkConfiguration;
}
private static string TryGetCountryCode()
{
try
{
return RegionInfo.CurrentRegion.TwoLetterISORegionName;
}
#pragma warning disable 0168
catch (Exception ignored)
#pragma warning restore 0168
{
// Ignored
}
return "US";
}
#endif
public static SdkConfiguration Create(IDictionary<string, object> eventProps)
@@ -463,6 +552,7 @@ public abstract class MaxSdkBase
public string AdapterClassName { get; private set; }
public string AdapterVersion { get; private set; }
public string SdkVersion { get; private set; }
public InitializationStatus InitializationStatus { get; private set; }
public MediatedNetworkInfo(IDictionary<string, object> mediatedNetworkDictionary)
{
@@ -471,6 +561,8 @@ public abstract class MaxSdkBase
AdapterClassName = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "adapterClassName", "");
AdapterVersion = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "adapterVersion", "");
SdkVersion = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "sdkVersion", "");
var initializationStatusInt = MaxSdkUtils.GetIntFromDictionary(mediatedNetworkDictionary, "initializationStatus", (int) InitializationStatus.NotInitialized);
InitializationStatus = InitializationStatusFromCode(initializationStatusInt);
}
public override string ToString()
@@ -478,7 +570,20 @@ public abstract class MaxSdkBase
return "[MediatedNetworkInfo name: " + Name +
", adapterClassName: " + AdapterClassName +
", adapterVersion: " + AdapterVersion +
", sdkVersion: " + SdkVersion + "]";
", sdkVersion: " + SdkVersion +
", initializationStatus: " + InitializationStatus + "]";
}
private static InitializationStatus InitializationStatusFromCode(int code)
{
if (Enum.IsDefined(typeof(InitializationStatus), code))
{
return (InitializationStatus) code;
}
else
{
return InitializationStatus.NotInitialized;
}
}
}
@@ -563,6 +668,11 @@ public abstract class MaxSdkBase
get { return MaxCmpService.Instance; }
}
internal static bool DisableAllLogs
{
get; private set;
}
protected static void ValidateAdUnitIdentifier(string adUnitIdentifier, string debugPurpose)
{
if (string.IsNullOrEmpty(adUnitIdentifier))
@@ -605,6 +715,15 @@ public abstract class MaxSdkBase
return new Rect(originX, originY, width, height);
}
protected static void HandleExtraParameter(string key, string value)
{
bool disableAllLogs;
if ("disable_all_logs".Equals(key) && bool.TryParse(value, out disableAllLogs))
{
DisableAllLogs = disableAllLogs;
}
}
/// <summary>
/// Handles forwarding callbacks from native to C#.
/// </summary>
@@ -622,7 +741,7 @@ public abstract class MaxSdkBase
var eventName = MaxSdkUtils.GetStringFromDictionary(eventProps, "name", "");
MaxSdkLogger.UserError("Unable to notify ad delegate due to an error in the publisher callback '" + eventName + "' due to exception: " + exception.Message);
Debug.LogException(exception);
MaxSdkLogger.LogException(exception);
}
}
@@ -642,6 +761,22 @@ public abstract class MaxSdkBase
return Json.Serialize(data);
}
#region Obsolete
[Obsolete("This API has been deprecated and will be removed in a future release. Please use AdViewPosition instead.")]
public enum BannerPosition
{
TopLeft,
TopCenter,
TopRight,
Centered,
CenterLeft,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight
}
[Obsolete("This API has been deprecated and will be removed in a future release.")]
public enum ConsentDialogState
{
@@ -649,6 +784,8 @@ public abstract class MaxSdkBase
Applies,
DoesNotApply
}
#endregion
}
/// <summary>
@@ -656,46 +793,6 @@ public abstract class MaxSdkBase
/// </summary>
internal static class AdPositionExtenstion
{
public static string ToSnakeCaseString(this MaxSdkBase.BannerPosition position)
{
if (position == MaxSdkBase.BannerPosition.TopLeft)
{
return "top_left";
}
else if (position == MaxSdkBase.BannerPosition.TopCenter)
{
return "top_center";
}
else if (position == MaxSdkBase.BannerPosition.TopRight)
{
return "top_right";
}
else if (position == MaxSdkBase.BannerPosition.Centered)
{
return "centered";
}
else if (position == MaxSdkBase.BannerPosition.CenterLeft)
{
return "center_left";
}
else if (position == MaxSdkBase.BannerPosition.CenterRight)
{
return "center_right";
}
else if (position == MaxSdkBase.BannerPosition.BottomLeft)
{
return "bottom_left";
}
else if (position == MaxSdkBase.BannerPosition.BottomCenter)
{
return "bottom_center";
}
else // position == MaxSdkBase.BannerPosition.BottomRight
{
return "bottom_right";
}
}
public static string ToSnakeCaseString(this MaxSdkBase.AdViewPosition position)
{
if (position == MaxSdkBase.AdViewPosition.TopLeft)
@@ -736,73 +833,3 @@ internal static class AdPositionExtenstion
}
}
}
namespace AppLovinMax.Internal.API
{
[Obsolete("This class has been deprecated and will be removed in a future SDK release.")]
public class CFError
{
public int Code { get; private set; }
public string Message { get; private set; }
public static CFError Create(int code = -1, string message = "")
{
return new CFError(code, message);
}
private CFError(int code, string message)
{
Code = code;
Message = message;
}
public override string ToString()
{
return "[CFError Code: " + Code +
", Message: " + Message + "]";
}
}
[Obsolete("This enum has been deprecated. Please use `MaxSdk.GetSdkConfiguration().ConsentFlowUserGeography` instead.")]
public enum CFType
{
Unknown,
Standard,
Detailed
}
public class CFService
{
[Obsolete("This property has been deprecated. Please use `MaxSdk.GetSdkConfiguration().ConsentFlowUserGeography` instead.")]
public static CFType CFType
{
get
{
switch (MaxSdk.GetSdkConfiguration().ConsentFlowUserGeography)
{
case MaxSdkBase.ConsentFlowUserGeography.Unknown:
return CFType.Unknown;
case MaxSdkBase.ConsentFlowUserGeography.Gdpr:
return CFType.Detailed;
case MaxSdkBase.ConsentFlowUserGeography.Other:
return CFType.Standard;
default:
throw new ArgumentOutOfRangeException();
}
}
}
[Obsolete("This method has been deprecated. Please use `MaxSdk.CmpService.ShowCmpForExistingUser` instead.")]
public static void SCF(Action<CFError> onFlowCompletedAction)
{
MaxSdkBase.CmpService.ShowCmpForExistingUser(error =>
{
if (onFlowCompletedAction == null) return;
var cfError = error == null ? null : CFError.Create((int) error.Code, error.Message);
onFlowCompletedAction(cfError);
});
}
}
}
File diff suppressed because it is too large Load Diff
+22 -8
View File
@@ -1,3 +1,4 @@
using System;
using UnityEngine;
public class MaxSdkLogger
@@ -10,6 +11,8 @@ public class MaxSdkLogger
/// </summary>
public static void UserDebug(string message)
{
if (MaxSdk.DisableAllLogs) return;
Debug.Log("Debug [" + SdkTag + "] " + message);
}
@@ -20,17 +23,18 @@ public class MaxSdkLogger
/// </summary>
public static void D(string message)
{
if (MaxSdk.IsVerboseLoggingEnabled())
{
if (MaxSdk.DisableAllLogs || !MaxSdk.IsVerboseLoggingEnabled()) return;
Debug.Log("Debug [" + SdkTag + "] " + message);
}
}
/// <summary>
/// Log warning messages.
/// </summary>
public static void UserWarning(string message)
{
if (MaxSdk.DisableAllLogs) return;
Debug.LogWarning("Warning [" + SdkTag + "] " + message);
}
@@ -41,17 +45,18 @@ public class MaxSdkLogger
/// </summary>
public static void W(string message)
{
if (MaxSdk.IsVerboseLoggingEnabled())
{
if (MaxSdk.DisableAllLogs || !MaxSdk.IsVerboseLoggingEnabled()) return;
Debug.LogWarning("Warning [" + SdkTag + "] " + message);
}
}
/// <summary>
/// Log error messages.
/// </summary>
public static void UserError(string message)
{
if (MaxSdk.DisableAllLogs) return;
Debug.LogError("Error [" + SdkTag + "] " + message);
}
@@ -62,9 +67,18 @@ public class MaxSdkLogger
/// </summary>
public static void E(string message)
{
if (MaxSdk.IsVerboseLoggingEnabled())
{
if (MaxSdk.DisableAllLogs || !MaxSdk.IsVerboseLoggingEnabled()) return;
Debug.LogError("Error [" + SdkTag + "] " + message);
}
/// <summary>
/// Log exceptions.
/// </summary>
public static void LogException(Exception exception)
{
if (MaxSdk.DisableAllLogs) return;
Debug.LogException(exception);
}
}
+52 -195
View File
@@ -35,11 +35,6 @@ public class MaxSdkUnityEditor : MaxSdkBase
private static readonly Dictionary<string, object> BannerPlacements = new Dictionary<string, object>();
private static readonly Dictionary<string, object> MRecPlacements = new Dictionary<string, object>();
public static MaxUserServiceUnityEditor UserService
{
get { return MaxUserServiceUnityEditor.Instance; }
}
[RuntimeInitializeOnLoadMethod]
public static void InitializeMaxSdkUnityEditorOnLoad()
{
@@ -112,7 +107,7 @@ public class MaxSdkUnityEditor : MaxSdkBase
{
if (_isInitialized)
{
Debug.LogError("Segment collection must be set before MAX SDK is initialized ");
MaxSdkLogger.UserError("Segment collection must be set before MAX SDK is initialized ");
}
}
@@ -256,15 +251,17 @@ public class MaxSdkUnityEditor : MaxSdkBase
/// Create a new banner.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
/// <param name="bannerPosition">Banner position. Must not be null.</param>
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
/// <param name="configuration">The configuration for the banner</param>
public static void CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
RequestAdUnit(adUnitIdentifier);
if (configuration.UseCoordinates) return;
if (_showStubAds && !StubBanners.ContainsKey(adUnitIdentifier))
{
CreateStubBanner(adUnitIdentifier, bannerPosition);
CreateStubBanner(adUnitIdentifier, configuration.Position);
}
ExecuteWithDelay(1f, () =>
@@ -275,28 +272,11 @@ public class MaxSdkUnityEditor : MaxSdkBase
});
}
/// <summary>
/// Create a new banner with a custom position.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
/// <param name="x">The X coordinate (horizontal position) of the banner relative to the top left corner of the screen.</param>
/// <param name="y">The Y coordinate (vertical position) of the banner relative to the top left corner of the screen.</param>
/// <seealso cref="GetBannerLayout">
/// The banner is placed within the safe area of the screen. You can use this to get the absolute position of the banner on screen.
/// </seealso>
public static void CreateBanner(string adUnitIdentifier, float x, float y)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
RequestAdUnit(adUnitIdentifier);
// TODO: Add stub ads support
}
private static void CreateStubBanner(string adUnitIdentifier, BannerPosition bannerPosition)
private static void CreateStubBanner(string adUnitIdentifier, AdViewPosition bannerPosition)
{
#if UNITY_EDITOR
// Only support BottomCenter and TopCenter for now
var bannerPrefabName = bannerPosition == BannerPosition.BottomCenter ? "BannerBottom" : "BannerTop";
var bannerPrefabName = bannerPosition == AdViewPosition.BottomCenter ? "BannerBottom" : "BannerTop";
var prefabPath = MaxSdkUtils.GetAssetPathForExportPath("MaxSdk/Prefabs/" + bannerPrefabName + ".prefab");
var bannerPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
var stubBanner = Object.Instantiate(bannerPrefab, Vector3.zero, Quaternion.identity);
@@ -362,7 +342,7 @@ public class MaxSdkUnityEditor : MaxSdkBase
/// </summary>
/// <param name="adUnitIdentifier">The ad unit identifier of the banner for which to update the position. Must not be null.</param>
/// <param name="bannerPosition">A new position for the banner. Must not be null.</param>
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
public static void UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition)
{
MaxSdkLogger.D("[AppLovin MAX] Updating banner position to '" + bannerPosition + "' for ad unit id '" + adUnitIdentifier + "'");
}
@@ -513,12 +493,14 @@ public class MaxSdkUnityEditor : MaxSdkBase
/// Create a new MREC.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
/// <param name="mrecPosition">MREC position. Must not be null.</param>
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
/// <param name="configuration">The configuration for the MREC.</param>
public static void CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
RequestAdUnit(adUnitIdentifier);
if (configuration.UseCoordinates) return;
ExecuteWithDelay(1f, () =>
{
var placement = MaxSdkUtils.GetStringFromDictionary(MRecPlacements, adUnitIdentifier);
@@ -527,21 +509,6 @@ public class MaxSdkUnityEditor : MaxSdkBase
});
}
/// <summary>
/// Create a new MREC with a custom position.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
/// <param name="x">The X coordinate (horizontal position) of the MREC relative to the top left corner of the screen.</param>
/// <param name="y">The Y coordinate (vertical position) of the MREC relative to the top left corner of the screen.</param>
/// <seealso cref="GetMRecLayout">
/// The MREC is placed within the safe area of the screen. You can use this to get the absolute position Rect of the MREC on screen.
/// </seealso>
public static void CreateMRec(string adUnitIdentifier, float x, float y)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
RequestAdUnit(adUnitIdentifier);
}
/// <summary>
/// Load a new MREC ad.
/// NOTE: The <see cref="CreateMRec()"/> method loads the first MREC ad and initiates an automated MREC refresh process.
@@ -1058,139 +1025,6 @@ public class MaxSdkUnityEditor : MaxSdkBase
#endregion
#region Rewarded Interstitial
/// <summary>
/// Start loading an rewarded interstitial ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to load. Must not be null.</param>
public static void LoadRewardedInterstitialAd(string adUnitIdentifier)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "load rewarded interstitial ad");
RequestAdUnit(adUnitIdentifier);
ExecuteWithDelay(1f, () =>
{
AddReadyAdUnit(adUnitIdentifier);
var eventProps = Json.Serialize(CreateBaseEventPropsDictionary("OnRewardedInterstitialAdLoadedEvent", adUnitIdentifier));
MaxSdkCallbacks.ForwardEvent(eventProps);
});
}
/// <summary>
/// Check if rewarded interstitial ad ad is loaded and ready to be displayed.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded ad to load. Must not be null.</param>
/// <returns>True if the ad is ready to be displayed</returns>
public static bool IsRewardedInterstitialAdReady(string adUnitIdentifier)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "check rewarded interstitial ad loaded");
if (!IsAdUnitRequested(adUnitIdentifier))
{
MaxSdkLogger.UserWarning("Rewarded interstitial ad '" + adUnitIdentifier +
"' was not requested, can not check if it is loaded");
return false;
}
return IsAdUnitReady(adUnitIdentifier);
}
/// <summary>
/// Present loaded rewarded interstitial ad for a given placement to tie ad events to. Note: if the rewarded interstitial ad is not ready to be displayed nothing will happen.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial to show. Must not be null.</param>
/// <param name="placement">The placement to tie the showing ad's events to</param>
/// <param name="customData">The custom data to tie the showing ad's events to. Maximum size is 8KB.</param>
public static void ShowRewardedInterstitialAd(string adUnitIdentifier, string placement = null, string customData = null)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "show rewarded interstitial ad");
if (!IsAdUnitRequested(adUnitIdentifier))
{
MaxSdkLogger.UserWarning("Rewarded interstitial ad '" + adUnitIdentifier +
"' was not requested, can not show it");
return;
}
if (!IsRewardedInterstitialAdReady(adUnitIdentifier))
{
MaxSdkLogger.UserWarning("Rewarded interstitial ad '" + adUnitIdentifier + "' is not ready, please check IsRewardedInterstitialAdReady() before showing.");
return;
}
RemoveReadyAdUnit(adUnitIdentifier);
if (_showStubAds)
{
ShowStubRewardedInterstitialAd(adUnitIdentifier, placement);
}
}
private static void ShowStubRewardedInterstitialAd(string adUnitIdentifier, string placement)
{
#if UNITY_EDITOR
var prefabPath = MaxSdkUtils.GetAssetPathForExportPath("MaxSdk/Prefabs/Rewarded.prefab");
var rewardedPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
var stubRewardedAd = Object.Instantiate(rewardedPrefab, Vector3.zero, Quaternion.identity);
var grantedReward = false;
var rewardedTitle = GameObject.Find("MaxRewardTitle").GetComponent<Text>();
var rewardStatus = GameObject.Find("MaxRewardStatus").GetComponent<Text>();
var closeButton = GameObject.Find("MaxRewardedCloseButton").GetComponent<Button>();
var rewardButton = GameObject.Find("MaxRewardButton").GetComponent<Button>();
Object.DontDestroyOnLoad(stubRewardedAd);
rewardedTitle.text = "MAX Rewarded Interstitial Ad:\n" + adUnitIdentifier;
closeButton.onClick.AddListener(() =>
{
if (grantedReward)
{
var rewardEventPropsDict = CreateBaseEventPropsDictionary("OnRewardedInterstitialAdReceivedRewardEvent", adUnitIdentifier, placement);
rewardEventPropsDict["rewardLabel"] = "coins";
rewardEventPropsDict["rewardAmount"] = "5";
var rewardEventProps = Json.Serialize(rewardEventPropsDict);
MaxSdkCallbacks.ForwardEvent(rewardEventProps);
}
var adHiddenEventProps = Json.Serialize(CreateBaseEventPropsDictionary("OnRewardedInterstitialAdHiddenEvent", adUnitIdentifier, placement));
MaxSdkCallbacks.ForwardEvent(adHiddenEventProps);
Object.Destroy(stubRewardedAd);
});
rewardButton.onClick.AddListener(() =>
{
grantedReward = true;
rewardStatus.text = "Reward granted. Will send reward callback on ad close.";
});
var adDisplayedEventProps = Json.Serialize(CreateBaseEventPropsDictionary("OnRewardedAdDisplayedEvent", adUnitIdentifier, placement));
MaxSdkCallbacks.ForwardEvent(adDisplayedEventProps);
#endif
}
/// <summary>
/// Set an extra parameter for the ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to set the extra parameter for. Must not be null.</param>
/// <param name="key">The key for the extra parameter. Must not be null.</param>
/// <param name="value">The value for the extra parameter.</param>
public static void SetRewardedInterstitialAdExtraParameter(string adUnitIdentifier, string key, string value)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded interstitial extra parameter");
}
/// <summary>
/// Set a local extra parameter for the ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to set the local extra parameter for. Must not be null.</param>
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
/// <param name="value">The value for the local extra parameter.</param>
public static void SetRewardedInterstitialAdLocalExtraParameter(string adUnitIdentifier, string key, object value)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded interstitial local extra parameter");
}
#endregion
#region Event Tracking
/// <summary>
@@ -1204,7 +1038,7 @@ public class MaxSdkUnityEditor : MaxSdkBase
#region Settings
private static bool _isMuted;
private static bool isMuted;
/// <summary>
/// Set whether to begin video ads in a muted state or not.
@@ -1214,7 +1048,7 @@ public class MaxSdkUnityEditor : MaxSdkBase
/// <param name="muted"><c>true</c> if video ads should being in muted state.</param>
public static void SetMuted(bool muted)
{
_isMuted = muted;
isMuted = muted;
}
/// <summary>
@@ -1225,7 +1059,7 @@ public class MaxSdkUnityEditor : MaxSdkBase
/// <returns><c>true</c> if video ads begin in muted state.</returns>
public static bool IsMuted()
{
return _isMuted;
return isMuted;
}
/// <summary>
@@ -1282,7 +1116,10 @@ public class MaxSdkUnityEditor : MaxSdkBase
/// </summary>
/// <param name="key">The key for the extra parameter. Must not be null.</param>
/// <param name="value">The value for the extra parameter. May be null.</param>
public static void SetExtraParameter(string key, string value) { }
public static void SetExtraParameter(string key, string value)
{
HandleExtraParameter(key, value);
}
/// <summary>
/// Get the native insets in pixels for the safe area.
@@ -1359,24 +1196,44 @@ public class MaxSdkUnityEditor : MaxSdkBase
#region Obsolete
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
{
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
CreateBanner(adUnitIdentifier, new AdViewConfiguration((AdViewPosition) bannerPosition));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateBanner(string adUnitIdentifier, float x, float y)
{
CreateBanner(adUnitIdentifier, new AdViewConfiguration(x, y));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition) instead.")]
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
{
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
UpdateBannerPosition(adUnitIdentifier, (AdViewPosition) bannerPosition);
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
{
CreateMRec(adUnitIdentifier, new AdViewConfiguration(mrecPosition));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateMRec(string adUnitIdentifier, float x, float y)
{
CreateMRec(adUnitIdentifier, new AdViewConfiguration(x, y));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.")]
public static void SetSdkKey(string sdkKey)
{
Debug.LogWarning("MaxSdk.SetSdkKey() has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.");
}
[Obsolete("This method has been deprecated. Please use `GetSdkConfiguration().ConsentDialogState`")]
public static ConsentDialogState GetConsentDialogState()
{
return ConsentDialogState.Unknown;
}
[Obsolete("This method has been deprecated. The AdInfo object is returned with ad callbacks.")]
public static AdInfo GetAdInfo(string adUnitIdentifier)
{
return new AdInfo(new Dictionary<string, object>());
}
#endregion
}
+44 -73
View File
@@ -12,7 +12,7 @@ using UnityEditor;
#endif
public class MaxSdkUtils
public static class MaxSdkUtils
{
/// <summary>
/// An Enum to be used when comparing two versions.
@@ -488,75 +488,11 @@ public class MaxSdkUtils
}
}
/// <summary>
/// Compares AppLovin MAX Unity mediation adapter plugin versions. Returns <see cref="VersionComparisonResult.Lesser"/>, <see cref="VersionComparisonResult.Equal"/>,
/// or <see cref="VersionComparisonResult.Greater"/> as the first version is less than, equal to, or greater than the second.
///
/// If a version for a specific platform is only present in one of the provided versions, the one that contains it is considered newer.
/// </summary>
/// <param name="versionA">The first version to be compared.</param>
/// <param name="versionB">The second version to be compared.</param>
/// <returns>
/// <see cref="VersionComparisonResult.Lesser"/> if versionA is less than versionB.
/// <see cref="VersionComparisonResult.Equal"/> if versionA and versionB are equal.
/// <see cref="VersionComparisonResult.Greater"/> if versionA is greater than versionB.
/// </returns>
public static VersionComparisonResult CompareUnityMediationVersions(string versionA, string versionB)
public static bool IsVersionInRange(string currentVersion, string minVersion, string maxVersion)
{
if (versionA.Equals(versionB)) return VersionComparisonResult.Equal;
// Unity version would be of format: android_w.x.y.z_ios_a.b.c.d
// For Android only versions it would be: android_w.x.y.z
// For iOS only version it would be: ios_a.b.c.d
// After splitting into their respective components, the versions would be at the odd indices.
var versionAComponents = versionA.Split('_').ToList();
var versionBComponents = versionB.Split('_').ToList();
var androidComparison = VersionComparisonResult.Equal;
if (versionA.Contains("android") && versionB.Contains("android"))
{
var androidVersionA = versionAComponents[1];
var androidVersionB = versionBComponents[1];
androidComparison = CompareVersions(androidVersionA, androidVersionB);
// Remove the Android version component so that iOS versions can be processed.
versionAComponents.RemoveRange(0, 2);
versionBComponents.RemoveRange(0, 2);
}
else if (versionA.Contains("android"))
{
androidComparison = VersionComparisonResult.Greater;
// Remove the Android version component so that iOS versions can be processed.
versionAComponents.RemoveRange(0, 2);
}
else if (versionB.Contains("android"))
{
androidComparison = VersionComparisonResult.Lesser;
// Remove the Android version component so that iOS version can be processed.
versionBComponents.RemoveRange(0, 2);
}
var iosComparison = VersionComparisonResult.Equal;
if (versionA.Contains("ios") && versionB.Contains("ios"))
{
var iosVersionA = versionAComponents[1];
var iosVersionB = versionBComponents[1];
iosComparison = CompareVersions(iosVersionA, iosVersionB);
}
else if (versionA.Contains("ios"))
{
iosComparison = VersionComparisonResult.Greater;
}
else if (versionB.Contains("ios"))
{
iosComparison = VersionComparisonResult.Lesser;
}
// If either one of the Android or iOS version is greater, the entire version should be greater.
return (androidComparison == VersionComparisonResult.Greater || iosComparison == VersionComparisonResult.Greater) ? VersionComparisonResult.Greater : VersionComparisonResult.Lesser;
var greaterThanOrEqualToMin = string.IsNullOrEmpty(minVersion) || MaxSdkUtils.CompareVersions(currentVersion, minVersion) != MaxSdkUtils.VersionComparisonResult.Lesser;
var lessThanOrEqualToMax = string.IsNullOrEmpty(maxVersion) || MaxSdkUtils.CompareVersions(currentVersion, maxVersion) != MaxSdkUtils.VersionComparisonResult.Greater;
return greaterThanOrEqualToMin && lessThanOrEqualToMax;
}
/// <summary>
@@ -644,6 +580,16 @@ public class MaxSdkUtils
return !string.IsNullOrEmpty(toCheck);
}
/// <summary>
/// Check if the given array is null or empty.
/// </summary>
/// <param name="array">The array to be checked.</param>
/// <returns><c>true</c> if the given array is <c>null</c> or has zero length.</returns>
public static bool IsNullOrEmpty<T>(T[] array)
{
return array == null || array.Length == 0;
}
#if UNITY_EDITOR
/// <summary>
/// Gets the path of the asset in the project for a given MAX plugin export path.
@@ -652,11 +598,36 @@ public class MaxSdkUtils
/// <returns>The exported path of the MAX plugin asset or the default export path if the asset is not found.</returns>
public static string GetAssetPathForExportPath(string exportPath)
{
var defaultPath = Path.Combine("Assets", exportPath);
var assetLabelToFind = "l:al_max_export_path-" + exportPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var assetGuids = AssetDatabase.FindAssets(assetLabelToFind);
var assetLabelToFind = "al_max_export_path-" + NormalizeToUnityPath(exportPath);
var labelSearchQuery = "l:" + assetLabelToFind;
var assetGuids = AssetDatabase.FindAssets(labelSearchQuery);
return assetGuids.Length < 1 ? defaultPath : AssetDatabase.GUIDToAssetPath(assetGuids[0]);
// Search all assets returned from the label query (may include partial matches)
foreach (var guid in assetGuids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadMainAssetAtPath(path);
if (asset == null) continue;
var labels = AssetDatabase.GetLabels(asset);
// Check if any label exactly matches
if (labels.Any(label => label == assetLabelToFind)) return path;
}
// Fall back to the default path if no exact label match is found
return Path.Combine("Assets", exportPath);
}
/// <summary>
/// Turns a path into a Unity compatible path by replacing backslashes with forward slashes.
/// This is important when dealing with Unity's AssetDatabase, which expects paths to use forward slashes.
/// </summary>
/// <param name="path">The path to normalize</param>
/// <returns>A Unity compatible normalized path with only forward slashes.</returns>
public static string NormalizeToUnityPath(string path)
{
return path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
#endif
}
+61 -171
View File
@@ -23,10 +23,6 @@ public class MaxSdkiOS : MaxSdkBase
}
#if UNITY_IOS
public static MaxUserServiceiOS UserService
{
get { return MaxUserServiceiOS.Instance; }
}
#region Initialization
@@ -94,7 +90,6 @@ public class MaxSdkiOS : MaxSdkBase
_MaxSetSegmentCollection(JsonUtility.ToJson(segmentCollection));
}
#endregion
#region MAX
@@ -255,35 +250,27 @@ public class MaxSdkiOS : MaxSdkBase
#region Banners
[DllImport("__Internal")]
private static extern void _MaxCreateBanner(string adUnitIdentifier, string bannerPosition);
private static extern void _MaxCreateBanner(string adUnitIdentifier, string bannerPosition, bool isAdaptive);
[DllImport("__Internal")]
private static extern void _MaxCreateBannerXY(string adUnitIdentifier, float x, float y, bool isAdaptive);
/// <summary>
/// Create a new banner.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
/// <param name="bannerPosition">Banner position. Must not be null.</param>
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
/// <param name="configuration">The configuration for the banner</param>
public static void CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
_MaxCreateBanner(adUnitIdentifier, bannerPosition.ToSnakeCaseString());
if (configuration.UseCoordinates)
{
_MaxCreateBannerXY(adUnitIdentifier, configuration.XCoordinate, configuration.YCoordinate, configuration.IsAdaptive);
}
[DllImport("__Internal")]
private static extern void _MaxCreateBannerXY(string adUnitIdentifier, float x, float y);
/// <summary>
/// Create a new banner with a custom position.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
/// <param name="x">The X coordinate (horizontal position) of the banner relative to the top left corner of the screen.</param>
/// <param name="y">The Y coordinate (vertical position) of the banner relative to the top left corner of the screen.</param>
/// <seealso cref="GetBannerLayout">
/// The banner is placed within the safe area of the screen. You can use this to get the absolute position of the banner on screen.
/// </seealso>
public static void CreateBanner(string adUnitIdentifier, float x, float y)
else
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
_MaxCreateBannerXY(adUnitIdentifier, x, y);
_MaxCreateBanner(adUnitIdentifier, configuration.Position.ToSnakeCaseString(), configuration.IsAdaptive);
}
}
[DllImport("__Internal")]
@@ -349,7 +336,7 @@ public class MaxSdkiOS : MaxSdkBase
/// </summary>
/// <param name="adUnitIdentifier">The ad unit identifier of the banner for which to update the position. Must not be null.</param>
/// <param name="bannerPosition">A new position for the banner. Must not be null.</param>
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
public static void UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "update banner position");
_MaxUpdateBannerPosition(adUnitIdentifier, bannerPosition.ToSnakeCaseString());
@@ -519,33 +506,25 @@ public class MaxSdkiOS : MaxSdkBase
[DllImport("__Internal")]
private static extern void _MaxCreateMRec(string adUnitIdentifier, string mrecPosition);
/// <summary>
/// Create a new MREC.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
/// <param name="mrecPosition">MREC position. Must not be null.</param>
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
_MaxCreateMRec(adUnitIdentifier, mrecPosition.ToSnakeCaseString());
}
[DllImport("__Internal")]
private static extern void _MaxCreateMRecXY(string adUnitIdentifier, float x, float y);
/// <summary>
/// Create a new MREC with a custom position.
/// Create a new MREC.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
/// <param name="x">The X coordinate (horizontal position) of the MREC relative to the top left corner of the screen.</param>
/// <param name="y">The Y coordinate (vertical position) of the MREC relative to the top left corner of the screen.</param>
/// <seealso cref="GetMRecLayout">
/// The MREC is placed within the safe area of the screen. You can use this to get the absolute position Rect of the MREC on screen.
/// </seealso>
public static void CreateMRec(string adUnitIdentifier, float x, float y)
/// <param name="configuration">The configuration for the MREC.</param>
public static void CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
_MaxCreateMRecXY(adUnitIdentifier, x, y);
if (configuration.UseCoordinates)
{
_MaxCreateMRecXY(adUnitIdentifier, configuration.XCoordinate, configuration.YCoordinate);
}
else
{
_MaxCreateMRec(adUnitIdentifier, configuration.Position.ToSnakeCaseString());
}
}
[DllImport("__Internal")]
@@ -1036,102 +1015,6 @@ public class MaxSdkiOS : MaxSdkBase
#endregion
#region Rewarded Interstitials
[DllImport("__Internal")]
private static extern void _MaxLoadRewardedInterstitialAd(string adUnitIdentifier);
/// <summary>
/// Start loading an rewarded interstitial ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to load. Must not be null.</param>
public static void LoadRewardedInterstitialAd(string adUnitIdentifier)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "load rewarded interstitial ad");
_MaxLoadRewardedInterstitialAd(adUnitIdentifier);
}
[DllImport("__Internal")]
private static extern bool _MaxIsRewardedInterstitialAdReady(string adUnitIdentifier);
/// <summary>
/// Check if rewarded interstitial ad ad is loaded and ready to be displayed.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to check if it's ready to be displayed. Must not be null.</param>
/// <returns>True if the ad is ready to be displayed</returns>
public static bool IsRewardedInterstitialAdReady(string adUnitIdentifier)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "check rewarded interstitial ad loaded");
return _MaxIsRewardedInterstitialAdReady(adUnitIdentifier);
}
[DllImport("__Internal")]
private static extern void _MaxShowRewardedInterstitialAd(string adUnitIdentifier, string placement, string customData);
/// <summary>
/// Present loaded rewarded interstitial ad for a given placement to tie ad events to. Note: if the rewarded interstitial ad is not ready to be displayed nothing will happen.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial to show. Must not be null.</param>
/// <param name="placement">The placement to tie the showing ad's events to</param>
/// <param name="customData">The custom data to tie the showing ad's events to. Maximum size is 8KB.</param>
public static void ShowRewardedInterstitialAd(string adUnitIdentifier, string placement = null, string customData = null)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "show rewarded interstitial ad");
if (IsRewardedInterstitialAdReady(adUnitIdentifier))
{
_MaxShowRewardedInterstitialAd(adUnitIdentifier, placement, customData);
}
else
{
MaxSdkLogger.UserWarning("Not showing MAX Ads rewarded interstitial ad: ad not ready");
}
}
[DllImport("__Internal")]
private static extern void _MaxSetRewardedInterstitialAdExtraParameter(string adUnitIdentifier, string key, string value);
/// <summary>
/// Set an extra parameter for the ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to set the extra parameter for. Must not be null.</param>
/// <param name="key">The key for the extra parameter. Must not be null.</param>
/// <param name="value">The value for the extra parameter.</param>
public static void SetRewardedInterstitialAdExtraParameter(string adUnitIdentifier, string key, string value)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded interstitial extra parameter");
_MaxSetRewardedInterstitialAdExtraParameter(adUnitIdentifier, key, value);
}
[DllImport("__Internal")]
private static extern void _MaxSetRewardedInterstitialAdLocalExtraParameter(string adUnitIdentifier, string key, IntPtr value);
[DllImport("__Internal")]
private static extern void _MaxSetRewardedInterstitialAdLocalExtraParameterJSON(string adUnitIdentifier, string key, string json);
/// <summary>
/// Set a local extra parameter for the ad.
/// </summary>
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded interstitial ad to set the local extra parameter for. Must not be null.</param>
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
/// <param name="value">The value for the local extra parameter. Accepts the following types: <see cref="IntPtr"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
public static void SetRewardedInterstitialAdLocalExtraParameter(string adUnitIdentifier, string key, object value)
{
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded interstitial ad local extra parameter");
if (value == null || value is IntPtr)
{
var intPtrValue = value == null ? IntPtr.Zero : (IntPtr) value;
_MaxSetRewardedInterstitialAdLocalExtraParameter(adUnitIdentifier, key, intPtrValue);
}
else
{
_MaxSetRewardedInterstitialAdLocalExtraParameterJSON(adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
}
}
#endregion
#region Event Tracking
[DllImport("__Internal")]
@@ -1144,7 +1027,9 @@ public class MaxSdkiOS : MaxSdkBase
/// <param name="parameters">A dictionary containing key-value pairs further describing this event.</param>
public static void TrackEvent(string name, IDictionary<string, string> parameters = null)
{
_MaxTrackEvent(name, Json.Serialize(parameters));
// Convert null to "{}" to avoid Unity sending the literal "null" to iOS.
var jsonString = ( parameters == null ) ? EmptyJson : Json.Serialize(parameters);
_MaxTrackEvent(name, jsonString);
}
#endregion
@@ -1256,6 +1141,8 @@ public class MaxSdkiOS : MaxSdkBase
/// <param name="value">The value for the extra parameter. May be null.</param>
public static void SetExtraParameter(string key, string value)
{
HandleExtraParameter(key, value);
_MaxSetExtraParameter(key, value);
}
@@ -1297,6 +1184,38 @@ public class MaxSdkiOS : MaxSdkBase
#region Obsolete
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
{
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
CreateBanner(adUnitIdentifier, new AdViewConfiguration((AdViewPosition) bannerPosition));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateBanner(string adUnitIdentifier, float x, float y)
{
CreateBanner(adUnitIdentifier, new AdViewConfiguration(x, y));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition) instead.")]
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
{
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
UpdateBannerPosition(adUnitIdentifier, (AdViewPosition) bannerPosition);
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
{
CreateMRec(adUnitIdentifier, new AdViewConfiguration(mrecPosition));
}
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
public static void CreateMRec(string adUnitIdentifier, float x, float y)
{
CreateMRec(adUnitIdentifier, new AdViewConfiguration(x, y));
}
[DllImport("__Internal")]
private static extern void _MaxSetSdkKey(string sdkKey);
@@ -1307,35 +1226,6 @@ public class MaxSdkiOS : MaxSdkBase
Debug.LogWarning("MaxSdk.SetSdkKey() has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.");
}
[DllImport("__Internal")]
private static extern int _MaxConsentDialogState();
[Obsolete("This method has been deprecated. Please use `GetSdkConfiguration().ConsentDialogState`")]
public static ConsentDialogState GetConsentDialogState()
{
if (!IsInitialized())
{
MaxSdkLogger.UserWarning(
"MAX Ads SDK has not been initialized yet. GetConsentDialogState() may return ConsentDialogState.Unknown");
}
return (ConsentDialogState) _MaxConsentDialogState();
}
[DllImport("__Internal")]
private static extern string _MaxGetAdInfo(string adUnitIdentifier);
[Obsolete("This method has been deprecated. The AdInfo object is returned with ad callbacks.")]
public static AdInfo GetAdInfo(string adUnitIdentifier)
{
var adInfoString = _MaxGetAdInfo(adUnitIdentifier);
if (string.IsNullOrEmpty(adInfoString)) return null;
var adInfoDictionary = Json.Deserialize(adInfoString) as Dictionary<string, object>;
return new AdInfo(adInfoDictionary);
}
#endregion
#endif
@@ -1 +0,0 @@
// This file has been deprecated and will be removed in a future plugin release.
-1
View File
@@ -1 +0,0 @@
// This file has been deprecated and will be removed in a future plugin release.
+1 -35
View File
@@ -1,35 +1 @@
using UnityEngine;
/// <summary>
/// This class has been deprecated and will be removed in a future release
/// </summary>
public class MaxUserServiceAndroid
{
private static readonly AndroidJavaClass _maxUnityPluginClass = new AndroidJavaClass("com.applovin.mediation.unity.MaxUnityPlugin");
private static readonly MaxUserServiceAndroid _instance = new MaxUserServiceAndroid();
public static MaxUserServiceAndroid Instance
{
get { return _instance; }
}
/// <summary>
/// Preload the user consent dialog. You have the option to call this so the consent dialog appears
/// more quickly when you call <see cref="MaxUserServiceAndroid.ShowConsentDialog"/>.
/// </summary>
public void PreloadConsentDialog()
{
_maxUnityPluginClass.CallStatic("preloadConsentDialog");
}
/// <summary>
/// Show the user consent dialog to the user using one from AppLovin's SDK. You should check that you actually need to show the consent dialog
/// by checking <see cref="MaxSdkBase.ConsentDialogState"/> in the completion block of <see cref="MaxSdkCallbacks.OnSdkInitializedEvent"/>.
/// Please make sure to implement the callback <see cref="MaxSdkCallbacks.OnSdkConsentDialogDismissedEvent"/>.
/// </summary>
public void ShowConsentDialog()
{
_maxUnityPluginClass.CallStatic("showConsentDialog");
}
}
// This file has been deprecated and will be removed in a future plugin release.
@@ -1,32 +1 @@
/// <summary>
/// This class has been deprecated and will be removed in a future release
/// </summary>
public class MaxUserServiceUnityEditor
{
private static readonly MaxUserServiceUnityEditor _instance = new MaxUserServiceUnityEditor();
public static MaxUserServiceUnityEditor Instance
{
get { return _instance; }
}
/// <summary>
/// Preload the user consent dialog. You have the option to call this so the consent dialog appears
/// more quickly when you call <see cref="MaxUserServiceUnityEditor.ShowConsentDialog"/>.
/// </summary>
public void PreloadConsentDialog()
{
MaxSdkLogger.UserWarning("The consent dialog cannot be pre-loaded in the Unity Editor. Please export the project to Android first.");
}
/// <summary>
/// Show the user consent dialog to the user using one from AppLovin's SDK. You should check that you actually need to show the consent dialog
/// by checking <see cref="SdkConfiguration.ConsentDialogState"/> in the completion block of <see cref="MaxSdkCallbacks.OnSdkInitializedEvent"/>.
/// Please make sure to implement the callback <see cref="MaxSdkCallbacks.OnSdkConsentDialogDismissedEvent"/>.
/// </summary>
public void ShowConsentDialog()
{
MaxSdkLogger.UserWarning("The consent dialog cannot be shown in the Unity Editor. Please export the project to Android first.");
}
}
// This file has been deprecated and will be removed in a future plugin release.
+1 -29
View File
@@ -1,29 +1 @@
#if UNITY_IOS
using System.Runtime.InteropServices;
/// <summary>
/// This class has been deprecated and will be removed in a future release
/// </summary>
public class MaxUserServiceiOS
{
private static readonly MaxUserServiceiOS _instance = new MaxUserServiceiOS();
public static MaxUserServiceiOS Instance
{
get { return _instance; }
}
[System.Obsolete("This version of the iOS consent flow has been deprecated as of MAX Unity Plugin v4.0.0 + iOS SDK v7.0.0, please refer to our documentation for enabling the new consent flow.")]
public void PreloadConsentDialog() {}
[DllImport("__Internal")]
private static extern void _MaxShowConsentDialog();
[System.Obsolete("This version of the iOS consent flow has been deprecated as of MAX Unity Plugin v4.0.0 + iOS SDK v7.0.0, please refer to our documentation for enabling the new consent flow.")]
public void ShowConsentDialog()
{
_MaxShowConsentDialog();
}
}
#endif
// This file has been deprecated and will be removed in a future plugin release.
+319
View File
@@ -0,0 +1,319 @@
//
// MaxWebRequest.cs
// AppLovin MAX Unity Plugin
//
// Created by Jonathan Liu on 6/10/2025.
// Copyright © 2025 AppLovin. All rights reserved.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
using AppLovinMax.ThirdParty.MiniJson;
namespace AppLovinMax.Internal
{
public enum WebRequestType
{
Get,
Post
}
public class WebRequestConfig
{
/// <summary>
/// Request endpoint. Task will not execute if one is not set.
/// </summary>
public string EndPoint { get; set; }
/// <summary>
/// Request method. GET is used by default.
/// </summary>
public WebRequestType RequestType { get; set; } = WebRequestType.Get;
/// <summary>
/// The download handler for the web request.
/// </summary>
public DownloadHandler DownloadHandler { get; set; } = new DownloadHandlerBuffer();
/// <summary>
/// Parameters that will be attached to the request.
/// </summary>
public Dictionary<string, string> QueryParams { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Headers that will be added to the request.
/// </summary>
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
/// <summary>
/// Request message data that will be sent with the request.
/// If both <see cref="Data"/> and <see cref="JsonString"/> are set, <see cref="Data"/> takes precedence and will be serialized to JSON.
/// </summary>
public object Data { get; set; } = null;
/// <summary>
/// Request message data in JSON format that will be sent with the request.
/// If both <see cref="Data"/> and <see cref="JsonString"/> are set, <see cref="Data"/> takes precedence and will be serialized to JSON.
/// </summary>
public string JsonString { get; set; } = "";
/// <summary>
/// The max number of attempts to make the web request before stopping.
/// </summary>
public int MaxRequestAttempts { get; set; } = 3;
/// <summary>
/// Timeout in seconds
/// </summary>
public int TimeoutSeconds { get; set; } = 60;
}
public class WebResponse
{
/// <summary>
/// Whether the request succeeded.
/// </summary>
public bool IsSuccess { get; } = false;
/// <summary>
/// The completed UnityWebRequest.
/// </summary>
public string ResponseMessage { get; } = "";
/// <summary>
/// The error message if the request failed.
/// </summary>
public string ErrorMessage { get; } = "";
public WebResponse(UnityWebRequest request)
{
if (request == null) return;
#if UNITY_2020_1_OR_NEWER
IsSuccess = request.result == UnityWebRequest.Result.Success;
#else
IsSuccess = !(request.isNetworkError || request.isHttpError);
#endif
// Only DownloadHandlerBuffer should try to access the text
if (request.downloadHandler is DownloadHandlerBuffer)
{
ResponseMessage = request.downloadHandler.text;
}
ErrorMessage = request.error;
}
}
public class MaxWebRequest
{
private const int WaitBetweenRetriesSeconds = 1;
private readonly WebRequestConfig webRequestConfig;
private UnityWebRequest webRequest;
private bool isSending;
public MaxWebRequest(WebRequestConfig config)
{
if (config == null)
{
MaxSdkLogger.E("WebRequestConfig cannot be null. Please provide a valid configuration.");
return;
}
webRequestConfig = config;
}
/// <summary>
/// Sends a web request using coroutines.
/// </summary>
/// <param name="callback">
/// A callback invoked with the resulting <see cref="WebResponse"/> object.
/// </param>
public IEnumerator Send(Action<WebResponse> callback)
{
yield return SendInternal(
request => request.SendAndWait(),
response => { callback?.Invoke(response); });
}
/// <summary>
/// Sends a web request synchronously and returns the response.
/// </summary>
/// <returns>Returns a <see cref="WebResponse"/> object.</returns>
public WebResponse SendSync()
{
var finalResponse = new WebResponse(null);
SendInternal(
waitFunc: request =>
{
request.SendWebRequest();
while (!request.isDone) { } // Block until the request is done
return null; // We don't use IEnumerator for sync version
},
onComplete: response => finalResponse = response
).MoveNext(); // Needed to start the loop (since it's still an IEnumerator)
return finalResponse;
}
public void Abort()
{
if (webRequest != null && !webRequest.isDone)
{
webRequest.Abort();
}
}
/// <summary>
/// Sends a web request using the current WebRequestConfig with automatic retries on failure.
/// </summary>
/// <param name="waitFunc">
/// The function to use for waiting on the web request to complete.
/// </param>
/// /// <param name="onComplete">
/// The callback invoked when the web request completes, with a <see cref="WebResponse"/> object containing the result.
/// </param>
private IEnumerator SendInternal(Func<UnityWebRequest, IEnumerator> waitFunc, Action<WebResponse> onComplete)
{
if (isSending || string.IsNullOrEmpty(webRequestConfig.EndPoint))
{
var errorString = isSending ? "Web Request currently being sent. Please send another request after the current one has finished." : "Web request endpoint is null or empty.";
MaxSdkLogger.E(errorString);
onComplete(new WebResponse(null));
}
isSending = true;
try
{
for (var attempt = 1; attempt <= webRequestConfig.MaxRequestAttempts; attempt++)
{
using (var request = CreateWebRequest())
{
// Hold a reference to the request so we can Abort the request if needed.
webRequest = request;
var wait = waitFunc(request);
if (wait != null)
yield return wait;
var webResponse = new WebResponse(request);
if (webResponse.IsSuccess)
{
onComplete(webResponse);
yield break;
}
if (attempt < webRequestConfig.MaxRequestAttempts)
{
MaxSdkLogger.UserWarning($"Error: {request.error}, Attempt {attempt} failed... Retrying request");
}
else
{
// All attempts have failed. Send error callback.
MaxSdkLogger.UserError($"Failed to make web request after {webRequestConfig.MaxRequestAttempts} attempts.");
onComplete(webResponse);
}
}
yield return new WaitForSeconds(WaitBetweenRetriesSeconds);
}
}
finally
{
webRequest = null;
isSending = false;
}
}
/// <summary>
/// Creates and returns a web request using the given configuration.
/// </summary>
/// <returns>Returns the web request that was created using the instance's WebRequestConfiguration.</returns>
private UnityWebRequest CreateWebRequest()
{
var url = BuildURL();
var request = new UnityWebRequest(url, webRequestConfig.RequestType.ToHttpMethodString())
{
downloadHandler = webRequestConfig.DownloadHandler,
timeout = webRequestConfig.TimeoutSeconds
};
// Set request upload data if needed
if (webRequestConfig.Data != null || MaxSdkUtils.IsValidString(webRequestConfig.JsonString))
{
var jsonString = webRequestConfig.Data != null ? Json.Serialize(webRequestConfig.Data) : webRequestConfig.JsonString;
var rawData = Encoding.UTF8.GetBytes(jsonString);
request.uploadHandler = new UploadHandlerRaw(rawData);
}
// Set request headers
foreach (var header in webRequestConfig.Headers)
{
request.SetRequestHeader(header.Key, header.Value);
}
return request;
}
/// <summary>
/// Builds a URL with the endpoint and query parameters from the instance's WebRequestConfiguration.
/// </summary>
/// <returns>Returns a formatted URL built using the endpoint and query parameters.</returns>
private string BuildURL()
{
if (webRequestConfig.QueryParams.Count == 0) return webRequestConfig.EndPoint;
var uriBuilder = new UriBuilder(webRequestConfig.EndPoint);
uriBuilder.Query = webRequestConfig.QueryParams.ToQueryString();
return uriBuilder.ToString();
}
}
public static class MaxWebRequestExtension
{
internal static IEnumerator SendAndWait(this UnityWebRequest request)
{
#if UNITY_EDITOR
var operation = request.SendWebRequest();
// In the Unity Editor, `yield return request.SendWebRequest()` fails, so we manually poll `isDone` in a loop.
while (!operation.isDone) yield return new WaitForSeconds(0.1f);
#else
yield return request.SendWebRequest();
#endif
}
internal static string ToHttpMethodString(this WebRequestType type)
{
switch (type)
{
case WebRequestType.Get:
return "GET";
case WebRequestType.Post:
return "POST";
default:
return "GET";
}
}
internal static string ToQueryString(this Dictionary<string, string> queries)
{
var queryBuilder = new StringBuilder();
foreach (var query in queries)
{
if (query.Key == null || query.Value == null) continue;
queryBuilder.Append(queryBuilder.Length == 0 ? "?" : "&");
queryBuilder.AppendFormat("{0}={1}", Uri.EscapeDataString(query.Key), Uri.EscapeDataString(query.Value));
}
return queryBuilder.ToString();
}
}
}
@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 8fb1f6f31b19c407b9d3fa62557d373a
guid: 9f1b03fdc97184fb98b605fe27346916
labels:
- al_max
- al_max_export_path-MaxSdk/Scripts/MaxUserSegment.cs
- al_max_export_path-MaxSdk/Scripts/MaxWebRequest.cs
MonoImporter:
externalObjects: {}
serializedVersion: 2
+60 -6
View File
@@ -439,25 +439,30 @@ namespace AppLovinMax.ThirdParty.MiniJson
/// <summary>
/// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
/// </summary>
/// <param name="json">A Dictionary&lt;string, object&gt; / List&lt;object&gt;</param>
/// <param name="obj">A Dictionary&lt;string, object&gt; / List&lt;object&gt;</param>
/// <param name="prettyPrint">Whether to pretty print the json string</param>
/// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
public static string Serialize(object obj)
public static string Serialize(object obj, bool prettyPrint = false)
{
return Serializer.Serialize(obj);
return Serializer.Serialize(obj, prettyPrint);
}
sealed class Serializer
{
StringBuilder builder;
bool prettyPrint;
int indentLevel;
Serializer()
Serializer(bool prettyPrint)
{
builder = new StringBuilder();
this.prettyPrint = prettyPrint;
indentLevel = 0;
}
public static string Serialize(object obj)
public static string Serialize(object obj, bool prettyPrint)
{
var instance = new Serializer();
var instance = new Serializer(prettyPrint);
instance.SerializeValue(obj);
@@ -506,21 +511,48 @@ namespace AppLovinMax.ThirdParty.MiniJson
builder.Append('{');
indentLevel++;
if (prettyPrint)
{
builder.AppendLine();
}
foreach (object e in obj.Keys)
{
if (!first)
{
builder.Append(',');
if (prettyPrint)
{
builder.AppendLine();
}
}
if (prettyPrint)
{
builder.Append(new string(' ', indentLevel * 4));
}
SerializeString(e.ToString());
builder.Append(':');
if (prettyPrint)
{
builder.Append(' ');
}
SerializeValue(obj[e]);
first = false;
}
indentLevel--;
if (prettyPrint)
{
builder.AppendLine();
builder.Append(new string(' ', indentLevel * 4));
}
builder.Append('}');
}
@@ -528,6 +560,12 @@ namespace AppLovinMax.ThirdParty.MiniJson
{
builder.Append('[');
indentLevel++;
if (prettyPrint)
{
builder.AppendLine();
}
bool first = true;
foreach (object obj in anArray)
@@ -535,6 +573,15 @@ namespace AppLovinMax.ThirdParty.MiniJson
if (!first)
{
builder.Append(',');
if (prettyPrint)
{
builder.AppendLine();
}
}
if (prettyPrint)
{
builder.Append(new string(' ', indentLevel * 4));
}
SerializeValue(obj);
@@ -542,6 +589,13 @@ namespace AppLovinMax.ThirdParty.MiniJson
first = false;
}
indentLevel--;
if (prettyPrint)
{
builder.AppendLine();
builder.Append(new string(' ', indentLevel * 4));
}
builder.Append(']');
}
@@ -1 +1 @@
{"consentFlowEnabled":false,"consentFlowPrivacyPolicyUrl":"","consentFlowTermsOfServiceUrl":"","overrideDefaultUserTrackingUsageDescriptions":false,"debugUserGeography":0,"userTrackingUsageDescriptionEn":"","userTrackingUsageLocalizationEnabled":false,"userTrackingUsageDescriptionDe":"","userTrackingUsageDescriptionEs":"","userTrackingUsageDescriptionFr":"","userTrackingUsageDescriptionJa":"","userTrackingUsageDescriptionKo":"","userTrackingUsageDescriptionZhHans":"","userTrackingUsageDescriptionZhHant":""}
{"consentFlowEnabled":false,"consentFlowPrivacyPolicyUrl":"","consentFlowTermsOfServiceUrl":"","shouldShowTermsAndPrivacyPolicyAlertInGDPR":false,"overrideDefaultUserTrackingUsageDescriptions":false,"debugUserGeography":0,"userTrackingUsageDescriptionEn":"","userTrackingUsageLocalizationEnabled":false,"userTrackingUsageDescriptionDe":"","userTrackingUsageDescriptionEs":"","userTrackingUsageDescriptionFr":"","userTrackingUsageDescriptionJa":"","userTrackingUsageDescriptionKo":"","userTrackingUsageDescriptionZhHans":"","userTrackingUsageDescriptionZhHant":""}