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}