001package com.intentsoftware.addapptr.ad; 002 003import android.app.Activity; 004import android.os.Build; 005import android.util.Log; 006import android.view.View; 007import android.view.ViewGroup; 008 009import com.intentsoftware.addapptr.AdNetwork; 010import com.intentsoftware.addapptr.module.Logger; 011import com.intentsoftware.addapptr.module.ViewableImpressionTracker; 012 013import java.util.HashMap; 014import java.util.Map; 015 016import androidx.annotation.CallSuper; 017import androidx.annotation.NonNull; 018 019public abstract class NativeAd extends Ad implements NativeAdData, View.OnAttachStateChangeListener, ViewableImpressionTracker.Delegate { 020 021 private ViewableImpressionTracker viewableImpressionTracker; 022 023 @SuppressWarnings("unused") 024 public static class NativeAdRating { 025 private final double value; 026 private final double scale; 027 028 public NativeAdRating(double value, double scale) { 029 this.value = value; 030 this.scale = scale; 031 } 032 033 public double getValue() { 034 return value; 035 } 036 037 public double getScale() { 038 return scale; 039 } 040 } 041 042 @SuppressWarnings("unused") 043 public enum Type { 044 APP_INSTALL, 045 CONTENT, 046 VIDEO, 047 UNIFIED, 048 OTHER, 049 UNKNOWN 050 } 051 052 public static final String ICON_IMAGE_ASSET = "icon"; 053 public static final String MAIN_IMAGE_ASSET = "main"; 054 055 public static final String TITLE_TEXT_ASSET = "headline"; 056 public static final String DESCRIPTION_TEXT_ASSET = "description"; 057 public static final String CALL_TO_ACTION_TEXT_ASSET = "cta"; 058 public static final String ADVERTISER_TEXT_ASSET = "advertiser"; 059 060 private final Map<String, String> assets = new HashMap<>(); 061 private NativeAdRating rating; 062 private LayoutListener layoutListener; 063 private View impressionTrackingView; 064 private View nativeAdView; 065 private boolean shouldReportImpression = true; 066 067 private boolean shouldRequestMainImage; 068 069 public final void setShouldRequestMainImage(boolean shouldRequestMainImage) { 070 this.shouldRequestMainImage = shouldRequestMainImage; 071 } 072 073 protected final boolean isShouldRequestMainImage() { 074 return shouldRequestMainImage; 075 } 076 077 protected final void setRating(NativeAdRating rating) { 078 this.rating = rating; 079 } 080 081 @Override 082 public final NativeAdRating getRating() { 083 return rating; 084 } 085 086 @Override 087 public final String getAsset(String assetName) { 088 return assets.get(assetName); 089 } 090 091 protected final void setAsset(String assetName, String assetValue) { 092 assets.put(assetName, assetValue); 093 } 094 095 @Override 096 public final AdNetwork getNetwork() { 097 return getConfig().getNetwork(); 098 } 099 100 @Override 101 @CallSuper 102 public void attachToLayout(ViewGroup layout, View mainImageView, View iconView, View ctaView) { 103 nativeAdView = layout; 104 if (layoutListener != null) { 105 layoutListener.onAdAttachedToLayout(this); 106 } 107 108 if (shouldReportImpression && layout != null) { 109 if (isExpired()) { 110 if (Logger.isLoggable(Log.WARN)) { 111 Logger.w(this, "Ad has expired, impression will not be counted."); 112 } 113 } else if (isAttachedToWindow(layout)) { 114 notifyListenerThatAdIsShown(); //native ad layout has already been added to window 115 } else { 116 startViewabilityTracking(layout); 117 } 118 } 119 } 120 121 private boolean isAttachedToWindow(View view) { 122 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 123 return view.isAttachedToWindow(); 124 } else { 125 return view.getWindowToken() != null; //logic taken from ViewCompat 126 } 127 } 128 129 @Override 130 public abstract View getBrandingLogo(); 131 132 @Override 133 public final void detachFromLayout() { 134 if (layoutListener != null) { 135 layoutListener.onAdDetachedFromLayout(this); 136 } 137 unload(); 138 } 139 140 @Override 141 public Type getAdType() { 142 return Type.UNKNOWN; 143 } 144 145 public final void setLayoutListener(LayoutListener layoutListener) { 146 this.layoutListener = layoutListener; 147 } 148 149 @Override 150 @CallSuper 151 public void unloadInternal() { //TODO: move to unload? 152 stopViewabilityTracking(); 153 stopViewableImpressionTracking(); 154 setInteractionListener(null); 155 setLoadListener(null); 156 setLayoutListener(null); 157 } 158 159 @NonNull 160 @Override 161 public String toString() { 162 return getClass().getSimpleName() + ";type:" + getAdType(); 163 } 164 165 @Override 166 protected final void finalize() throws Throwable { 167 unload(); 168 super.finalize(); 169 } 170 171 private synchronized void startViewabilityTracking(View trackingView) { 172 if (trackingView != null) { 173 impressionTrackingView = trackingView; 174 impressionTrackingView.addOnAttachStateChangeListener(this); 175 } else { 176 if (Logger.isLoggable(Log.ERROR)) { 177 Logger.e(this, "Cannot start viewability tracking, native ad view is null"); 178 } 179 } 180 } 181 182 private void startViewableImpressionTracking() { 183 if (nativeAdView != null) { 184 if (isExpired()) { 185 if (Logger.isLoggable(Log.WARN)) { 186 Logger.w(this, "Ad has expired, viewable impression will not be counted."); 187 } 188 } else { 189 viewableImpressionTracker = new ViewableImpressionTracker(this, nativeAdView); 190 } 191 } else { 192 if (Logger.isLoggable(Log.ERROR)) { 193 Logger.e(this, "Cannot start viewable tracking, native ad view is null"); 194 } 195 } 196 } 197 198 @Override 199 @CallSuper 200 public void pause() { 201 super.pause(); 202 if (viewableImpressionTracker != null) { 203 viewableImpressionTracker.pause(); 204 } 205 } 206 207 @Override 208 @CallSuper 209 public void resume(Activity activity) { 210 super.resume(activity); 211 if (viewableImpressionTracker != null) { 212 viewableImpressionTracker.resume(); 213 } 214 } 215 216 @Override 217 protected final void notifyListenerThatAdIsShown() { 218 super.notifyListenerThatAdIsShown(); 219 startViewableImpressionTracking(); 220 } 221 222 @Override 223 protected final void notifyListenerThatAdWasClicked() { 224 super.notifyListenerThatAdWasClicked(); 225 if (viewableImpressionTracker != null) { 226 onViewableImpressionDetected(); 227 } 228 } 229 230 @Override 231 public final void onViewAttachedToWindow(View view) { 232 stopViewabilityTracking(); //we are just interested in the first impression 233 if (shouldReportImpression) { 234 shouldReportImpression = false; 235 if (isExpired()) { 236 if (Logger.isLoggable(Log.WARN)) { 237 Logger.w(this, "Ad has expired, impression will not be counted."); 238 } 239 } else { 240 notifyListenerThatAdIsShown(); 241 } 242 } 243 } 244 245 @Override 246 public final void onViewDetachedFromWindow(View view) { 247 } 248 249 @Override 250 public final void onViewableImpressionDetected() { 251 stopViewableImpressionTracking(); 252 notifyListenerViewableImpression(); 253 } 254 255 @Override 256 public final boolean hasExpired() { 257 return isExpired(); 258 } 259 260 private void stopViewabilityTracking() { 261 if (impressionTrackingView != null) { 262 impressionTrackingView.removeOnAttachStateChangeListener(this); 263 impressionTrackingView = null; 264 } 265 } 266 267 private void stopViewableImpressionTracking() { 268 if (viewableImpressionTracker != null) { 269 viewableImpressionTracker.destroy(); 270 viewableImpressionTracker = null; 271 } 272 if (nativeAdView != null) { 273 nativeAdView = null; 274 } 275 } 276 277 public interface LayoutListener { 278 void onAdAttachedToLayout(Ad ad); 279 280 void onAdDetachedFromLayout(Ad ad); 281 } 282}