diff --git a/java/admob/FullScreenNativeExample/app/build.gradle b/java/admob/FullScreenNativeExample/app/build.gradle index faa193f26..c4dff2ef6 100644 --- a/java/admob/FullScreenNativeExample/app/build.gradle +++ b/java/admob/FullScreenNativeExample/app/build.gradle @@ -18,16 +18,11 @@ android { proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" } } + buildFeatures { viewBinding = true } } dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.media3:media3-exoplayer:1.3.1' - implementation 'androidx.media3:media3-exoplayer-dash:1.3.1' - implementation 'androidx.media3:media3-ui:1.3.1' - implementation 'androidx.media3:media3-session:1.3.1' implementation 'com.google.android.gms:play-services-ads:23.4.0' - implementation 'androidx.media3:media3-common:1.3.1' } diff --git a/java/admob/FullScreenNativeExample/app/src/main/AndroidManifest.xml b/java/admob/FullScreenNativeExample/app/src/main/AndroidManifest.xml index 8319e474a..a0450c233 100644 --- a/java/admob/FullScreenNativeExample/app/src/main/AndroidManifest.xml +++ b/java/admob/FullScreenNativeExample/app/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - + android:theme="@style/Theme.AppCompat.Light"> @@ -21,9 +19,8 @@ android:value="true" /> + android:name="com.google.example.gms.fullscreennativeexample.MainActivity" + android:exported="true"> diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/AdsViewHolder.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/AdsViewHolder.java deleted file mode 100644 index 1f3015c29..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/AdsViewHolder.java +++ /dev/null @@ -1,112 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import com.google.android.gms.ads.VideoController; -import com.google.android.gms.ads.VideoController.VideoLifecycleCallbacks; -import com.google.android.gms.ads.nativead.MediaView; -import com.google.android.gms.ads.nativead.NativeAd; -import com.google.android.gms.ads.nativead.NativeAdView; -import com.google.example.gms.fullscreennativeexample.FeedAdapter.AdFeedItem; - -/** A ViewHolder representing a native ad view. */ -public class AdsViewHolder extends FeedViewHolder { - - public AdsViewHolder(View itemView) { - super(itemView); - } - - public void bind(AdFeedItem adFeedItem, int position) { - populateNativeAdView(adFeedItem.getNativeAd(), itemView.findViewById(R.id.native_ad_view)); - } - - @Override - public void attach() {} - - @Override - public void detach() {} - - public static void populateNativeAdView(NativeAd nativeAd, NativeAdView adView) { - MediaView mediaView = adView.findViewById(R.id.ad_media); - // Set the media view. - adView.setMediaView(mediaView); - - // Set other ad assets. - adView.setHeadlineView(adView.findViewById(R.id.ad_headline)); - adView.setBodyView(adView.findViewById(R.id.ad_body)); - adView.setCallToActionView(adView.findViewById(R.id.ad_call_to_action)); - ImageView imageView = adView.findViewById(R.id.ad_app_icon); - imageView.setClipToOutline(true); - adView.setIconView(imageView); - - // The headline and mediaContent are guaranteed to be in every NativeAd. - ((TextView) adView.getHeadlineView()).setText(nativeAd.getHeadline()); - mediaView.setMediaContent(nativeAd.getMediaContent()); - - // These assets aren't guaranteed to be in every NativeAd, so it's important to - // check before trying to display them. - if (nativeAd.getBody() == null) { - adView.getBodyView().setVisibility(View.INVISIBLE); - } else { - adView.getBodyView().setVisibility(View.VISIBLE); - ((TextView) adView.getBodyView()).setText(nativeAd.getBody()); - } - - if (nativeAd.getCallToAction() == null) { - adView.getCallToActionView().setVisibility(View.INVISIBLE); - } else { - adView.getCallToActionView().setVisibility(View.VISIBLE); - ((Button) adView.getCallToActionView()).setText(nativeAd.getCallToAction()); - } - - if (nativeAd.getIcon() == null) { - adView.getIconView().setVisibility(View.GONE); - } else { - ((ImageView) adView.getIconView()).setImageDrawable(nativeAd.getIcon().getDrawable()); - adView.getIconView().setVisibility(View.VISIBLE); - } - - // This method tells the Google Mobile Ads SDK that you have finished populating your - // native ad view with this native ad. - adView.setNativeAd(nativeAd); - - // Get the video controller for the ad. One will always be provided, - // even if the ad doesn't have a video asset. - VideoController videoController = nativeAd.getMediaContent().getVideoController(); - - // Updates the UI to say whether or not this ad has a video asset. - if (videoController.hasVideoContent()) { - - // Create a new VideoLifecycleCallbacks object and pass it to the VideoController. - // The VideoController will call methods on this object when events occur in the - // video lifecycle. - videoController.setVideoLifecycleCallbacks( - new VideoLifecycleCallbacks() { - @Override - public void onVideoEnd() { - // Publishers should allow native ads to complete video playback before - // refreshing or replacing them with another ad in the same UI location. - super.onVideoEnd(); - } - }); - } - } -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/ContentViewHolder.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/ContentViewHolder.java deleted file mode 100644 index 8cc99ee07..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/ContentViewHolder.java +++ /dev/null @@ -1,100 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.net.Uri; -import android.util.Log; -import android.view.View; -import androidx.annotation.OptIn; -import androidx.media3.common.MediaItem; -import androidx.media3.common.Player; -import androidx.media3.common.util.UnstableApi; -import androidx.media3.datasource.RawResourceDataSource; -import androidx.media3.exoplayer.ExoPlayer; -import androidx.media3.ui.PlayerView; -import com.google.example.gms.fullscreennativeexample.FeedAdapter.ContentFeedItem; - -/** A ViewHolder representing video content. */ -public class ContentViewHolder extends FeedViewHolder { - private final PlayerView playerView; - private final ExoPlayer player; - - public ContentViewHolder(View itemView) { - super(itemView); - // Define click listener for the ContentViewHolder's View. - itemView.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - Log.d(VideoFeedsActivity.TAG, "Element " + getAbsoluteAdapterPosition() + " clicked."); - } - }); - - player = new ExoPlayer.Builder(itemView.getContext()).build(); - player.setRepeatMode(Player.REPEAT_MODE_ALL); - playerView = (PlayerView) itemView.findViewById(R.id.player_view); - playerView.setPlayer(player); - playerView.setUseController(false); - player.prepare(); - itemView - .findViewById(R.id.overlay) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - Log.d(VideoFeedsActivity.TAG, "Overlay clicked."); - if (player.isPlaying()) { - player.pause(); - } else { - player.play(); - } - } - }); - } - /** Gets a reference to the Player instance. */ - public Player getPlayer() { - return player; - } - - /** Gets a reference to the PlayerView instance. */ - public PlayerView getPlayerView() { - return playerView; - } - - /** Expecting the id of raw resource video file. */ - @OptIn(markerClass = UnstableApi.class) - public void bind(ContentFeedItem contentFeedItem, int position) { - Player player = this.getPlayer(); - Uri uri = RawResourceDataSource.buildRawResourceUri(contentFeedItem.getResourceId()); - player.setMediaItem(MediaItem.fromUri(uri)); - } - - /** Starts playing the video when the ViewHolder is visible. */ - @Override - public void attach() { - getPlayer().prepare(); - getPlayer().play(); - Log.d(VideoFeedsActivity.TAG, "Playing " + getLayoutPosition()); - } - - /** Stops playing the video when the ViewHolder is no longer visible. */ - @Override - public void detach() { - getPlayer().stop(); - Log.d(VideoFeedsActivity.TAG, "Stopping # " + getLayoutPosition()); - } -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/FeedAdapter.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/FeedAdapter.java deleted file mode 100644 index d8bdf7e74..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/FeedAdapter.java +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import com.google.android.gms.ads.nativead.NativeAd; -import java.util.ArrayList; - -/** Provide views to RecyclerView with data from mDataSet. */ -public class FeedAdapter extends RecyclerView.Adapter { - private final ArrayList mDataSet = new ArrayList<>(); - - /** Base class of the feed instance. */ - public abstract static class FeedItem { - - /** The feed item is either a video content or an ad. */ - public enum Type { - CONTENT, - AD - } - - /** Gets the type of feed item. */ - abstract Type getType(); - } - - /** Content type FeedItem. */ - public static class ContentFeedItem extends FeedItem { - private final int resourceId; - - public ContentFeedItem(int resourceId) { - this.resourceId = resourceId; - } - - public int getResourceId() { - return resourceId; - } - - @Override - Type getType() { - return Type.CONTENT; - } - } - - /** Ad type FeedItem. */ - public static class AdFeedItem extends FeedItem { - private final NativeAd nativeAd; - - public AdFeedItem(@NonNull NativeAd nativeAd) { - this.nativeAd = nativeAd; - } - - public NativeAd getNativeAd() { - return nativeAd; - } - - @Override - Type getType() { - return Type.AD; - } - } - - /** Initialize the dataset of the Adapter. */ - public FeedAdapter() { - mDataSet.add(new ContentFeedItem(R.raw.clip_landscape)); - mDataSet.add(new ContentFeedItem(R.raw.clip_portrait)); - mDataSet.add(new ContentFeedItem(R.raw.clip_square)); - } - - public void add(FeedItem feedItem) { - mDataSet.add(feedItem); - notifyDataSetChanged(); - } - - @Override - public FeedViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - View itemView; - // Create a new view. - if (FeedItem.Type.values()[viewType] == FeedItem.Type.AD) { - itemView = - LayoutInflater.from(viewGroup.getContext()) - .inflate(R.layout.ad_unified, viewGroup, false); - return new AdsViewHolder(itemView); - } - itemView = - LayoutInflater.from(viewGroup.getContext()) - .inflate(R.layout.video_row_item, viewGroup, false); - return new ContentViewHolder(itemView); - } - - @Override - public void onBindViewHolder(FeedViewHolder viewHolder, final int position) { - // Get element from your dataset at this position and replace the contents of the view - // with that element - FeedItem feedItem = mDataSet.get(position); - switch (feedItem.getType()) { - case AD: - ((AdsViewHolder) viewHolder).bind((AdFeedItem) feedItem, position); - break; - case CONTENT: - ((ContentViewHolder) viewHolder).bind((ContentFeedItem) feedItem, position); - break; - default: - break; - } - } - - @Override - public int getItemCount() { - return mDataSet.size(); - } - - @Override - public int getItemViewType(int position) { - return mDataSet.get(position).getType().ordinal(); - } - - @Override - public void onViewAttachedToWindow(FeedViewHolder holder) { - super.onViewAttachedToWindow(holder); - holder.attach(); - } - - @Override - public void onViewDetachedFromWindow(FeedViewHolder holder) { - super.onViewDetachedFromWindow(holder); - holder.detach(); - } -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/FeedViewHolder.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/FeedViewHolder.java deleted file mode 100644 index aa497f312..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/FeedViewHolder.java +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.view.View; -import androidx.recyclerview.widget.RecyclerView; - -/** ViewHolder instance which holds the actual feed. */ -public abstract class FeedViewHolder extends RecyclerView.ViewHolder { - public FeedViewHolder(View view) { - super(view); - } - - public abstract void attach(); - - public abstract void detach(); -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/GoogleMobileAdsConsentManager.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/GoogleMobileAdsConsentManager.java new file mode 100644 index 000000000..21768adaf --- /dev/null +++ b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/GoogleMobileAdsConsentManager.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.example.gms.fullscreennativeexample; + +import android.app.Activity; +import android.content.Context; +import com.google.android.ump.ConsentDebugSettings; +import com.google.android.ump.ConsentForm.OnConsentFormDismissedListener; +import com.google.android.ump.ConsentInformation; +import com.google.android.ump.ConsentInformation.PrivacyOptionsRequirementStatus; +import com.google.android.ump.ConsentRequestParameters; +import com.google.android.ump.FormError; +import com.google.android.ump.UserMessagingPlatform; + +/** + * The Google Mobile Ads SDK provides the User Messaging Platform (Google's IAB Certified consent + * management platform) as one solution to capture consent for users in GDPR impacted countries. + * This is an example and you can choose another consent management platform to capture consent. + */ +public class GoogleMobileAdsConsentManager { + private static GoogleMobileAdsConsentManager instance; + private final ConsentInformation consentInformation; + + /** Private constructor */ + private GoogleMobileAdsConsentManager(Context context) { + this.consentInformation = UserMessagingPlatform.getConsentInformation(context); + } + + /** Public constructor */ + public static GoogleMobileAdsConsentManager getInstance(Context context) { + if (instance == null) { + instance = new GoogleMobileAdsConsentManager(context); + } + + return instance; + } + + /** Interface definition for a callback to be invoked when consent gathering is complete. */ + public interface OnConsentGatheringCompleteListener { + void consentGatheringComplete(FormError error); + } + + /** Helper variable to determine if the app can request ads. */ + public boolean canRequestAds() { + return consentInformation.canRequestAds(); + } + + /** Helper variable to determine if the privacy options form is required. */ + public boolean isPrivacyOptionsRequired() { + return consentInformation.getPrivacyOptionsRequirementStatus() + == PrivacyOptionsRequirementStatus.REQUIRED; + } + + /** + * Helper method to call the UMP SDK methods to request consent information and load/present a + * consent form if necessary. + */ + public void gatherConsent( + Activity activity, OnConsentGatheringCompleteListener onConsentGatheringCompleteListener) { + // For testing purposes, you can force a DebugGeography of EEA or NOT_EEA. + ConsentDebugSettings debugSettings = + new ConsentDebugSettings.Builder(activity) + // .setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA) + .addTestDeviceHashedId(MainActivity.TEST_DEVICE_HASHED_ID) + .build(); + + ConsentRequestParameters params = + new ConsentRequestParameters.Builder().setConsentDebugSettings(debugSettings).build(); + + // Requesting an update to consent information should be called on every app launch. + consentInformation.requestConsentInfoUpdate( + activity, + params, + () -> + UserMessagingPlatform.loadAndShowConsentFormIfRequired( + activity, + formError -> { + // Consent has been gathered. + onConsentGatheringCompleteListener.consentGatheringComplete(formError); + }), + requestConsentError -> + onConsentGatheringCompleteListener.consentGatheringComplete(requestConsentError)); + } + + /** Helper method to call the UMP SDK method to present the privacy options form. */ + public void showPrivacyOptionsForm( + Activity activity, OnConsentFormDismissedListener onConsentFormDismissedListener) { + UserMessagingPlatform.showPrivacyOptionsForm(activity, onConsentFormDismissedListener); + } +} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/MainActivity.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/MainActivity.java new file mode 100644 index 000000000..ae3da2579 --- /dev/null +++ b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/MainActivity.java @@ -0,0 +1,179 @@ +package com.google.example.gms.fullscreennativeexample; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.PopupMenu; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentManager; +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdLoader; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.MobileAds; +import com.google.android.gms.ads.RequestConfiguration; +import com.google.android.gms.ads.nativead.NativeAd; +import com.google.example.gms.fullscreennativeexample.databinding.ActivityMainBinding; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +/** An activity class that loads a native ad. */ +@SuppressLint("SetTextI18n") +public class MainActivity extends AppCompatActivity { + + // Check your logcat output for the test device hashed ID e.g. + // "Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("ABCDEF012345")) + // to get test ads on this device" or + // "Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("ABCDEF012345") to set this as + // a debug device". + public static final String TEST_DEVICE_HASHED_ID = "ABCDEF012345"; + public GoogleMobileAdsConsentManager googleMobileAdsConsentManager; + + private static final String AD_UNIT_ID = "ca-app-pub-3940256099942544/2247696110"; + private static final String TAG = "MainActivity"; + private final AtomicBoolean isMobileAdsInitializeCalled = new AtomicBoolean(false); + private final FragmentManager fragmentManager = getSupportFragmentManager(); + private NativeAd nativeAd; + + public NativeAd getNativeAd() { + return nativeAd; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + Log.d(TAG, "Google Mobile Ads SDK Version: " + MobileAds.getVersion()); + + googleMobileAdsConsentManager = + GoogleMobileAdsConsentManager.getInstance(getApplicationContext()); + googleMobileAdsConsentManager.gatherConsent( + this, + consentError -> { + if (consentError != null) { + // Consent not obtained in current session. + Log.w( + TAG, + String.format("%s: %s", consentError.getErrorCode(), consentError.getMessage())); + } + + if (googleMobileAdsConsentManager.canRequestAds()) { + MainFragment mainFragment = + (MainFragment) fragmentManager.findFragmentById(R.id.fragment_container_view); + if (mainFragment != null) { + mainFragment.binding.loadAdButton.setVisibility(View.VISIBLE); + } + + initializeMobileAdsSdk(); + } + + if (googleMobileAdsConsentManager.isPrivacyOptionsRequired()) { + // Regenerate the options menu to include a privacy setting. + invalidateOptionsMenu(); + } + }); + + // This sample attempts to load ads using consent obtained in the previous session. + if (googleMobileAdsConsentManager.canRequestAds()) { + initializeMobileAdsSdk(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.action_menu, menu); + MenuItem moreMenu = menu.findItem(R.id.action_more); + moreMenu.setVisible(googleMobileAdsConsentManager.isPrivacyOptionsRequired()); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + View menuItemView = findViewById(item.getItemId()); + PopupMenu popup = new PopupMenu(this, menuItemView); + popup.getMenuInflater().inflate(R.menu.popup_menu, popup.getMenu()); + popup.show(); + popup.setOnMenuItemClickListener( + popupMenuItem -> { + if (popupMenuItem.getItemId() != R.id.privacy_settings) { + return false; + } + + // Handle changes to user consent. + googleMobileAdsConsentManager.showPrivacyOptionsForm( + this, + formError -> { + if (formError != null) { + Toast.makeText(this, formError.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + return true; + }); + return super.onOptionsItemSelected(item); + } + + private void initializeMobileAdsSdk() { + if (isMobileAdsInitializeCalled.getAndSet(true)) { + return; + } + + // Set your test devices. + MobileAds.setRequestConfiguration( + new RequestConfiguration.Builder() + .setTestDeviceIds(Collections.singletonList(TEST_DEVICE_HASHED_ID)) + .build()); + + new Thread( + () -> { + // Initialize the Google Mobile Ads SDK on a background thread. + MobileAds.initialize(this, initializationStatus -> {}); + + // Load an ad on the main thread. + runOnUiThread(this::loadAd); + }) + .start(); + } + + public void loadAd() { + MainFragment mainFragment = + (MainFragment) fragmentManager.findFragmentById(R.id.fragment_container_view); + if (mainFragment != null) { + mainFragment.binding.loadAdButton.setEnabled(false); + } + + AdLoader adLoader = + new AdLoader.Builder(this, AD_UNIT_ID) + .forNativeAd(nativeAd -> { + MainActivity.this.nativeAd = nativeAd; + if (mainFragment != null) { + mainFragment.binding.showAdButton.setEnabled(true); + } + }) + .withAdListener(new AdListener() { + @Override + public void onAdFailedToLoad(@NonNull LoadAdError adError) { + if (mainFragment != null) { + mainFragment.binding.loadAdButton.setEnabled(true); + } + Log.d( + TAG, + adError.getCode() + "Native ad failed to load : " + adError); + Toast.makeText( + MainActivity.this, + adError.getMessage(), + Toast.LENGTH_SHORT) + .show(); + } + }) + .build(); + + adLoader.loadAd(new AdRequest.Builder().build()); + } +} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/MainFragment.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/MainFragment.java new file mode 100644 index 000000000..cf8df59b4 --- /dev/null +++ b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/MainFragment.java @@ -0,0 +1,70 @@ +package com.google.example.gms.fullscreennativeexample; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import com.google.example.gms.fullscreennativeexample.databinding.FragmentMainBinding; + +/** A fragment class that provides the UI to load/show a native ad. */ +public class MainFragment extends Fragment { + + public FragmentMainBinding binding; + + public MainFragment() { + super(R.layout.fragment_main); + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FragmentMainBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // Configure "Load ad" button. + binding.loadAdButton.setOnClickListener( + unusedView -> { + binding.loadAdButton.setEnabled(false); + binding.showAdButton.setEnabled(false); + + MainActivity mainActivity = ((MainActivity) getActivity()); + if (mainActivity != null) { + mainActivity.loadAd(); + } + }); + + // Configure "Show ad" button. + binding.showAdButton.setOnClickListener( + unusedView -> { + binding.loadAdButton.setEnabled(true); + binding.showAdButton.setEnabled(false); + + // Display the full-screen native ad. + final FragmentTransaction fragmentTransaction = + getParentFragmentManager().beginTransaction(); + NativeAdFragment nativeAdFragment = new NativeAdFragment(); + fragmentTransaction.add(R.id.fragment_container_view, nativeAdFragment); + fragmentTransaction.show(nativeAdFragment).addToBackStack(null); + fragmentTransaction.commit(); + } + ); + } +} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/NativeAdFragment.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/NativeAdFragment.java new file mode 100644 index 000000000..985c5c600 --- /dev/null +++ b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/NativeAdFragment.java @@ -0,0 +1,124 @@ +package com.google.example.gms.fullscreennativeexample; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.google.android.gms.ads.VideoController; +import com.google.android.gms.ads.VideoController.VideoLifecycleCallbacks; +import com.google.android.gms.ads.nativead.NativeAd; +import com.google.android.gms.ads.nativead.NativeAdView; +import com.google.example.gms.fullscreennativeexample.databinding.FragmentNativeAdBinding; + +/** A fragment class that represents the native ad. */ +public class NativeAdFragment extends Fragment { + + private FragmentNativeAdBinding binding; + + public NativeAdFragment() { + super(R.layout.fragment_native_ad); + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FragmentNativeAdBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + binding = null; + MainActivity mainActivity = ((MainActivity) getActivity()); + if (mainActivity != null && mainActivity.getSupportActionBar() != null) { + mainActivity.getSupportActionBar().show(); + } + super.onDestroyView(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + MainActivity mainActivity = ((MainActivity) getActivity()); + if (mainActivity != null) { + populateNativeAdView(mainActivity.getNativeAd(), binding.innerLayout.nativeAdView); + + if (mainActivity.getSupportActionBar() != null) { + // Display the native ad in full-screen. + mainActivity.getSupportActionBar().hide(); + } + } + } + + private void populateNativeAdView(NativeAd nativeAd, NativeAdView adView) { + // Set the media view. + adView.setMediaView(binding.innerLayout.adMedia); + + // Set other ad assets. + adView.setHeadlineView(binding.innerLayout.adHeadline); + adView.setBodyView(binding.innerLayout.adBody); + adView.setCallToActionView(binding.innerLayout.adCallToAction); + ImageView imageView = binding.innerLayout.adAppIcon; + imageView.setClipToOutline(true); + adView.setIconView(imageView); + + // The headline and mediaContent are guaranteed to be in every NativeAd. + binding.innerLayout.adHeadline.setText(nativeAd.getHeadline()); + binding.innerLayout.adMedia.setMediaContent(nativeAd.getMediaContent()); + + // These assets aren't guaranteed to be in every NativeAd, so it's important to + // check before trying to display them. + if (nativeAd.getBody() == null) { + binding.innerLayout.adBody.setVisibility(View.INVISIBLE); + binding.innerLayout.adBody.setText(""); + } else { + binding.innerLayout.adBody.setVisibility(View.VISIBLE); + binding.innerLayout.adBody.setText(nativeAd.getBody()); + } + + if (nativeAd.getCallToAction() == null) { + binding.innerLayout.adCallToAction.setVisibility(View.INVISIBLE); + binding.innerLayout.adBody.setText(""); + } else { + binding.innerLayout.adCallToAction.setVisibility(View.VISIBLE); + binding.innerLayout.adCallToAction.setText(nativeAd.getCallToAction()); + } + + if (nativeAd.getIcon() == null) { + binding.innerLayout.adAppIcon.setVisibility(View.GONE); + } else { + binding.innerLayout.adAppIcon.setVisibility(View.VISIBLE); + binding.innerLayout.adAppIcon.setImageDrawable(nativeAd.getIcon().getDrawable()); + } + + // This method tells the Google Mobile Ads SDK that you have finished populating your + // native ad view with this native ad. + adView.setNativeAd(nativeAd); + + VideoController videoController = nativeAd.getMediaContent().getVideoController(); + + // Updates the UI to say whether or not this ad has a video asset. + if (videoController.hasVideoContent()) { + + // Create a new VideoLifecycleCallbacks object and pass it to the VideoController. + // The VideoController will call methods on this object when events occur in the + // video lifecycle. + videoController.setVideoLifecycleCallbacks( + new VideoLifecycleCallbacks() { + @Override + public void onVideoEnd() { + // Publishers should allow native ads to complete video playback before + // refreshing or replacing them with another ad in the same UI location. + super.onVideoEnd(); + } + }); + } + } +} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/NativeAdsPool.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/NativeAdsPool.java deleted file mode 100644 index 3165d501a..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/NativeAdsPool.java +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.content.Context; -import android.util.Log; -import androidx.annotation.NonNull; -import com.google.android.gms.ads.AdListener; -import com.google.android.gms.ads.AdLoader; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.LoadAdError; -import com.google.android.gms.ads.MediaAspectRatio; -import com.google.android.gms.ads.MobileAds; -import com.google.android.gms.ads.RequestConfiguration; -import com.google.android.gms.ads.VideoOptions; -import com.google.android.gms.ads.nativead.NativeAd; -import com.google.android.gms.ads.nativead.NativeAdOptions; -import java.util.Arrays; -import java.util.concurrent.LinkedBlockingQueue; - -/** - * A simple implementation of cache for preloaded native ads, this is not supposed to be used in any - * production use case. - */ -public class NativeAdsPool { - - // Check your logcat output for the test device hashed ID e.g. - // "Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("ABCDEF012345")) - // to get test ads on this device" or - // "Use new ConsentDebugSettings.Builder().addTestDeviceHashedId("ABCDEF012345") to set this as - // a debug device". - public static final String TEST_DEVICE_HASHED_ID = "ABCDEF012345"; - - private static final String ADMOB_AD_UNIT_ID = "ca-app-pub-3940256099942544/7342230711"; - - private final Context context; - private LinkedBlockingQueue nativeAdsPool = null; - private AdLoader adLoader; - - /** Listen to the refresh event from pool to handle new feeds. */ - public interface OnPoolRefreshedListener { - public void onPoolRefreshed(); - } - - public NativeAdsPool(Context context) { - this.context = context; - nativeAdsPool = new LinkedBlockingQueue<>(); - } - - public void init(final OnPoolRefreshedListener listener) { - // Set your test devices. - MobileAds.setRequestConfiguration( - new RequestConfiguration.Builder() - .setTestDeviceIds(Arrays.asList(TEST_DEVICE_HASHED_ID)) - .build()); - - new Thread( - () -> { - // Initialize the Google Mobile Ads SDK on a background thread. - MobileAds.initialize(this.context, initializationStatus -> {}); - }) - .start(); - - AdLoader.Builder builder = new AdLoader.Builder(this.context, ADMOB_AD_UNIT_ID); - VideoOptions videoOptions = - new VideoOptions.Builder().setStartMuted(false).setCustomControlsRequested(true).build(); - NativeAdOptions adOptions = - new NativeAdOptions.Builder() - .setMediaAspectRatio(MediaAspectRatio.PORTRAIT) - .setVideoOptions(videoOptions) - .build(); - builder.withNativeAdOptions(adOptions); - builder.forNativeAd( - nativeAd -> { - push(nativeAd); - listener.onPoolRefreshed(); - }); - builder.withAdListener( - new AdListener() { - @Override - public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) { - Log.d("XXX", loadAdError.toString()); - } - }); - adLoader = builder.build(); - } - - public void push(NativeAd ad) { - nativeAdsPool.add(ad); - } - - public NativeAd pop() { - return nativeAdsPool.poll(); - } - - public void refresh(int numberOfAds) { - this.adLoader.loadAds(new AdRequest.Builder().build(), numberOfAds); - } -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/RecyclerViewFragment.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/RecyclerViewFragment.java deleted file mode 100644 index 38a8b7fe4..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/RecyclerViewFragment.java +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.PagerSnapHelper; -import androidx.recyclerview.widget.RecyclerView; -import com.google.example.gms.fullscreennativeexample.FeedAdapter.AdFeedItem; -import com.google.example.gms.fullscreennativeexample.NativeAdsPool.OnPoolRefreshedListener; - -/** Demonstrates the use of {@link RecyclerView} with a {@link LinearLayoutManager}. */ -public class RecyclerViewFragment extends Fragment { - - private static final String TAG = "RecyclerViewFragment"; - - protected RecyclerView recyclerView; - protected FeedAdapter feedAdapter; - protected RecyclerView.LayoutManager layoutManager; - private NativeAdsPool nativeAdsPool; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.recycler_view_frag, container, false); - rootView.setTag(TAG); - - // initialize RecyclerView - recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view); - // use LinearLayoutManager - recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - // set FeedAdapter as the adapter for RecyclerView. - feedAdapter = new FeedAdapter(); - recyclerView.setAdapter(feedAdapter); - // setup PagerSnapHelper - new PagerSnapHelper().attachToRecyclerView(recyclerView); - nativeAdsPool = new NativeAdsPool(this.getContext()); - nativeAdsPool.init( - (OnPoolRefreshedListener) () -> feedAdapter.add(new AdFeedItem(nativeAdsPool.pop()))); - nativeAdsPool.refresh(5); - return rootView; - } -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/VideoFeedsActivity.java b/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/VideoFeedsActivity.java deleted file mode 100644 index 2ebddd97f..000000000 --- a/java/admob/FullScreenNativeExample/app/src/main/java/com/google/example/gms/fullscreennativeexample/VideoFeedsActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (C) 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package com.google.example.gms.fullscreennativeexample; - -import android.os.Bundle; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentTransaction; - -/** Main FragmentActivity for the video feeds. */ -public class VideoFeedsActivity extends FragmentActivity { - - public static final String TAG = "VideoFeedsActivity"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.video_feeds_activity); - - if (savedInstanceState == null) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - RecyclerViewFragment fragment = new RecyclerViewFragment(); - transaction.replace(R.id.video_feed_content_fragment, fragment); - transaction.commit(); - } - } -} diff --git a/java/admob/FullScreenNativeExample/app/src/main/res/layout/activity_main.xml b/java/admob/FullScreenNativeExample/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..774c80400 --- /dev/null +++ b/java/admob/FullScreenNativeExample/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,6 @@ + diff --git a/java/admob/FullScreenNativeExample/app/src/main/res/layout/fragment_main.xml b/java/admob/FullScreenNativeExample/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 000000000..761270c96 --- /dev/null +++ b/java/admob/FullScreenNativeExample/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,40 @@ + + + + + +