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