2020import android .graphics .drawable .Drawable ;
2121import android .text .TextUtils ;
2222import android .util .AttributeSet ;
23+ import android .util .Log ;
2324import android .view .LayoutInflater ;
2425import android .widget .Button ;
2526import android .widget .FrameLayout ;
2627import android .widget .ImageView ;
2728import android .widget .RatingBar ;
2829import android .widget .TextView ;
2930import androidx .annotation .Nullable ;
31+ import androidx .annotation .VisibleForTesting ;
3032import androidx .constraintlayout .widget .ConstraintLayout ;
3133import com .google .android .gms .ads .nativead .MediaView ;
3234import com .google .android .gms .ads .nativead .NativeAd ;
3335import com .google .android .gms .ads .nativead .NativeAdView ;
3436import com .google .unity .ads .R ;
3537
36- /** Base class for a template view. */
38+ /**
39+ * Base class for a template view. Inspired from:
40+ * http://google3/java/com/google/android/libraries/admob/demo/native_templates/NativeTemplatesAndroid/nativetemplates/src/main/java/com/google/android/ads/nativetemplates/TemplateView.java
41+ * (resource ids may differ).
42+ */
3743public final class TemplateView extends FrameLayout {
3844
45+ private static final String TAG = TemplateView .class .getSimpleName ();
46+
47+ @ VisibleForTesting (otherwise = VisibleForTesting .PRIVATE )
48+ static final String MEDIUM_TEMPLATE = "medium_template" ;
49+
50+ private static final String SMALL_TEMPLATE = "small_template" ;
51+
3952 private int templateType ;
4053 private NativeTemplateStyle styles ;
4154 private NativeAd nativeAd ;
@@ -49,9 +62,7 @@ public final class TemplateView extends FrameLayout {
4962 private MediaView mediaView ;
5063 private Button callToActionView ;
5164 private ConstraintLayout background ;
52-
53- private static final String MEDIUM_TEMPLATE = "medium_template" ;
54- private static final String SMALL_TEMPLATE = "small_template" ;
65+ private LayoutInflater layoutInflater ;
5566
5667 public TemplateView (Context context ) {
5768 super (context );
@@ -67,11 +78,68 @@ public TemplateView(Context context, @Nullable AttributeSet attrs, int defStyleA
6778 initView (context , attrs );
6879 }
6980
70- public TemplateView (Context context , AttributeSet attrs , int defStyleAttr , int defStyleRes ) {
81+ public TemplateView (
82+ Context context , @ Nullable AttributeSet attrs , int defStyleAttr , int defStyleRes ) {
83+ super (context , attrs , defStyleAttr , defStyleRes );
84+ initView (context , attrs );
85+ }
86+
87+ @ VisibleForTesting (otherwise = VisibleForTesting .NONE )
88+ public TemplateView (
89+ Context context ,
90+ @ Nullable AttributeSet attrs ,
91+ int defStyleAttr ,
92+ int defStyleRes ,
93+ LayoutInflater layoutInflater ) {
7194 super (context , attrs , defStyleAttr , defStyleRes );
95+ this .layoutInflater = layoutInflater ;
7296 initView (context , attrs );
7397 }
7498
99+ private void initView (Context context , @ Nullable AttributeSet attributeSet ) {
100+ if (attributeSet == null ) {
101+ return ;
102+ }
103+ // See http://shortn/_b5UYcrWluD for more details.
104+ TypedArray typedArray =
105+ context
106+ .getTheme ()
107+ .obtainStyledAttributes (
108+ /* set= */ attributeSet ,
109+ /* attrs= */ R .styleable .TemplateView ,
110+ /* defStyleAttr= */ 0 ,
111+ /* defStyleRes= */ 0 );
112+ if (typedArray == null ) {
113+ return ;
114+ }
115+ int templateTypeResource = R .styleable .TemplateView_gnt_template_type ;
116+ int templateViewResource = R .layout .gnt_medium_template_view ;
117+ try {
118+ templateType = typedArray .getResourceId (templateTypeResource , templateViewResource );
119+ } catch (RuntimeException e ) {
120+ Log .e (
121+ TAG ,
122+ String .format (
123+ "Failed to get template type from attribute resources (templateTypeResource: %d, "
124+ + "templateViewResource: %d)." ,
125+ templateTypeResource , templateViewResource ),
126+ e );
127+ // Rethrow to let the exception propagate up the stack.
128+ throw e ;
129+ } finally {
130+ typedArray .recycle ();
131+ }
132+
133+ if (layoutInflater == null ) {
134+ setLayoutInflater (context );
135+ }
136+ layoutInflater .inflate (templateType , this );
137+ }
138+
139+ private void setLayoutInflater (Context context ) {
140+ layoutInflater = (LayoutInflater ) context .getSystemService (Context .LAYOUT_INFLATER_SERVICE );
141+ }
142+
75143 public NativeTemplateStyle getStyles () {
76144 return styles ;
77145 }
@@ -182,6 +250,17 @@ private void applyStyles() {
182250 requestLayout ();
183251 }
184252
253+ private boolean areAllViewsInitialized () {
254+ return nativeAdView != null
255+ && callToActionView != null
256+ && primaryView != null
257+ && secondaryView != null
258+ && tertiaryView != null
259+ && mediaView != null
260+ && iconView != null
261+ && ratingBar != null ;
262+ }
263+
185264 private boolean adHasOnlyStore (NativeAd nativeAd ) {
186265 String store = nativeAd .getStore ();
187266 String advertiser = nativeAd .getAdvertiser ();
@@ -191,6 +270,12 @@ private boolean adHasOnlyStore(NativeAd nativeAd) {
191270 public void setNativeAd (NativeAd nativeAd ) {
192271 this .nativeAd = nativeAd ;
193272
273+ if (!areAllViewsInitialized ()) {
274+ // Defensive check against potential NPEs if a view has not been initialized (which is
275+ // expected if the template view was solely constructed from context).
276+ return ;
277+ }
278+
194279 String store = nativeAd .getStore ();
195280 String advertiser = nativeAd .getAdvertiser ();
196281 String headline = nativeAd .getHeadline ();
@@ -199,24 +284,22 @@ public void setNativeAd(NativeAd nativeAd) {
199284 Double starRating = nativeAd .getStarRating ();
200285 NativeAd .Image icon = nativeAd .getIcon ();
201286
202- String secondaryText ;
203- callToActionView .setText (cta );
204-
205287 nativeAdView .setCallToActionView (callToActionView );
206288 nativeAdView .setHeadlineView (primaryView );
207289 nativeAdView .setMediaView (mediaView );
208290 secondaryView .setVisibility (VISIBLE );
291+
292+ String secondaryText = "" ;
209293 if (adHasOnlyStore (nativeAd )) {
210294 nativeAdView .setStoreView (secondaryView );
211295 secondaryText = store ;
212296 } else if (!TextUtils .isEmpty (advertiser )) {
213297 nativeAdView .setAdvertiserView (secondaryView );
214298 secondaryText = advertiser ;
215- } else {
216- secondaryText = "" ;
217299 }
218300
219301 primaryView .setText (headline );
302+ callToActionView .setText (cta );
220303
221304 // Set the secondary view to be the star rating if available.
222305 if (starRating != null && starRating > 0 ) {
@@ -253,7 +336,9 @@ public void setNativeAd(NativeAd nativeAd) {
253336 * https://developers.google.com/admob/android/native-unified#destroy_ad
254337 */
255338 public void destroyNativeAd () {
256- nativeAd .destroy ();
339+ if (nativeAd != null ) {
340+ nativeAd .destroy ();
341+ }
257342 }
258343
259344 public String getTemplateTypeName () {
@@ -265,22 +350,6 @@ public String getTemplateTypeName() {
265350 return "" ;
266351 }
267352
268- private void initView (Context context , AttributeSet attributeSet ) {
269- TypedArray attributes =
270- context .getTheme ().obtainStyledAttributes (attributeSet , R .styleable .TemplateView , 0 , 0 );
271-
272- try {
273- templateType =
274- attributes .getResourceId (
275- R .styleable .TemplateView_gnt_template_type , R .layout .gnt_medium_template_view );
276- } finally {
277- attributes .recycle ();
278- }
279- LayoutInflater inflater =
280- (LayoutInflater ) context .getSystemService (Context .LAYOUT_INFLATER_SERVICE );
281- inflater .inflate (templateType , this );
282- }
283-
284353 @ Override
285354 public void onFinishInflate () {
286355 super .onFinishInflate ();
0 commit comments