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}