diff --git a/android/src/main/java/com/getcapacitor/community/admob/banner/BannerExecutor.java b/android/src/main/java/com/getcapacitor/community/admob/banner/BannerExecutor.java index 3671a6ba..6f35a291 100644 --- a/android/src/main/java/com/getcapacitor/community/admob/banner/BannerExecutor.java +++ b/android/src/main/java/com/getcapacitor/community/admob/banner/BannerExecutor.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; @@ -12,6 +13,8 @@ import androidx.annotation.NonNull; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.util.Supplier; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; import com.getcapacitor.JSObject; import com.getcapacitor.PluginCall; import com.getcapacitor.community.admob.helpers.AdViewIdHelper; @@ -43,18 +46,19 @@ public BannerExecutor( } public void initialize() { + // The first child of android.R.id.content in a Capacitor activity is a CoordinatorLayout. mViewGroup = (ViewGroup) ((ViewGroup) activitySupplier.get().findViewById(android.R.id.content)).getChildAt(0); } public void showBanner(final PluginCall call) { final AdOptions adOptions = AdOptions.getFactory().createBannerOptions(call); - float density = contextSupplier.get().getResources().getDisplayMetrics().density; + final float density = contextSupplier.get().getResources().getDisplayMetrics().density; - int defaultWidthPixels = contextSupplier.get().getResources().getDisplayMetrics().widthPixels; + final int defaultWidthPixels = contextSupplier.get().getResources().getDisplayMetrics().widthPixels; - DisplayMetrics metrics = new DisplayMetrics(); + final DisplayMetrics metrics = new DisplayMetrics(); activitySupplier.get().getWindowManager().getDefaultDisplay().getRealMetrics(metrics); - int realWidthPixels = metrics.widthPixels; + final int realWidthPixels = metrics.widthPixels; boolean fullscreen = false; if ((activitySupplier.get().getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0) { @@ -63,10 +67,10 @@ public void showBanner(final PluginCall call) { if (mAdView != null) { updateExistingAdView(adOptions); + call.resolve(); return; } - // Why a try catch block? try { mAdView = new AdView(contextSupplier.get()); @@ -79,66 +83,65 @@ public void showBanner(final PluginCall call) { ); } - // Setup AdView Layout + // ---- Container that will consume navigation bar insets ---- mAdViewLayout = new RelativeLayout(contextSupplier.get()); mAdViewLayout.setHorizontalGravity(Gravity.CENTER_HORIZONTAL); mAdViewLayout.setVerticalGravity(Gravity.BOTTOM); - final CoordinatorLayout.LayoutParams mAdViewLayoutParams = new CoordinatorLayout.LayoutParams( + final boolean isLandscape = + contextSupplier.get().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + + // Parent is CoordinatorLayout → use its LayoutParams + final CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams( CoordinatorLayout.LayoutParams.WRAP_CONTENT, CoordinatorLayout.LayoutParams.WRAP_CONTENT ); - // TODO: Make an enum like the AdSizeEnum? - switch (adOptions.position) { - case "TOP_CENTER": - mAdViewLayoutParams.gravity = Gravity.TOP; - break; - case "CENTER": - mAdViewLayoutParams.gravity = Gravity.CENTER; - break; - default: - mAdViewLayoutParams.gravity = Gravity.BOTTOM; - break; + // Gravity per position, but left-align in LANDSCAPE (unless explicitly CENTER) + if ("CENTER".equals(adOptions.position)) { + lp.gravity = Gravity.CENTER; + } else if ("TOP_CENTER".equals(adOptions.position)) { + lp.gravity = isLandscape ? (Gravity.TOP | Gravity.START) : Gravity.TOP; + } else { + // default (bottom) + lp.gravity = isLandscape ? (Gravity.BOTTOM | Gravity.START) : Gravity.BOTTOM; } - // set Safe Area - View rootView = activitySupplier.get().getWindow().getDecorView(); - rootView.setOnApplyWindowInsetsListener((v, insets) -> { - int bottomInset = insets.getSystemWindowInsetBottom(); - int topInset = insets.getSystemWindowInsetTop(); + // Margins + final int densityMargin = (int) (adOptions.margin * density); + final int configuredAdWidthPx = (int) (adOptions.adSize.getSize().getWidth() * density); - if ("TOP_CENTER".equals(adOptions.position)) { - mAdViewLayoutParams.setMargins(0, topInset, 0, 0); + if (isLandscape) { + // Left-align: no centering—just use regular margins + lp.setMargins(densityMargin, densityMargin, densityMargin, densityMargin); + } else { + // PORTRAIT: keep your existing centering logic + if (configuredAdWidthPx <= 0 || adOptions.adSize.toString().equals("ADAPTIVE_BANNER")) { + int sideMargin = 0; + if (fullscreen) { + sideMargin = (realWidthPixels - defaultWidthPixels) / 2; + } + lp.setMargins(sideMargin, densityMargin, sideMargin, densityMargin); } else { - mAdViewLayoutParams.setMargins(0, 0, 0, bottomInset); + int sideMargin = (defaultWidthPixels - configuredAdWidthPx) / 2; + if (fullscreen) { + sideMargin = (realWidthPixels - configuredAdWidthPx) / 2; + } + lp.setMargins(sideMargin, densityMargin, sideMargin, densityMargin); } + } - mAdViewLayout.setLayoutParams(mAdViewLayoutParams); + mAdViewLayout.setLayoutParams(lp); + + // Consume the navigation bar inset so the banner clears the gesture/3-button bar + ViewCompat.setOnApplyWindowInsetsListener(mAdViewLayout, (v, insets) -> { + final int navBottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; + // keep existing paddings; only adjust bottom + v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), navBottom); return insets; }); - mAdViewLayout.setLayoutParams(mAdViewLayoutParams); - - int densityMargin = (int) (adOptions.margin * density); - - // Center Banner Ads - int adWidth = (int) (adOptions.adSize.getSize().getWidth() * density); - - if (adWidth <= 0 || adOptions.adSize.toString().equals("ADAPTIVE_BANNER")) { - int margin = 0; - if (fullscreen) { - margin = (realWidthPixels - defaultWidthPixels) / 2; - } - mAdViewLayoutParams.setMargins(margin, densityMargin, margin, densityMargin); - } else { - int sideMargin = ((int) defaultWidthPixels - adWidth) / 2; - if (fullscreen) { - sideMargin = (realWidthPixels - adWidth) / 2; - } - mAdViewLayoutParams.setMargins(sideMargin, densityMargin, sideMargin, densityMargin); - } - + // Proceed to create and attach the ad view createNewAdView(adOptions); call.resolve(); @@ -162,7 +165,6 @@ public void hideBanner(final PluginCall call) { mAdView.pause(); final BannerAdSizeInfo sizeInfo = new BannerAdSizeInfo(0, 0); - notifyListeners(BannerAdPluginEvents.SizeChanged.getWebEventName(), sizeInfo); call.resolve(); @@ -233,23 +235,28 @@ private void updateExistingAdView(AdOptions adOptions) { * https://developers.google.com/admob/ios/banner?hl=ja */ private void createNewAdView(AdOptions adOptions) { - // Run AdMob In Main UI Thread activitySupplier .get() .runOnUiThread(() -> { final AdRequest adRequest = RequestHelper.createRequest(adOptions); + // Assign the correct id needed AdViewIdHelper.assignIdToAdView(mAdView, adOptions, adRequest, logTag, contextSupplier.get()); - // Add the AdView to the view hierarchy. + + // Add the AdView to the container mAdViewLayout.addView(mAdView); + + // Add container above WebView and request insets now that it's attached + mViewGroup.addView(mAdViewLayout); + mAdViewLayout.bringToFront(); + ViewCompat.requestApplyInsets(mAdViewLayout); + // Start loading the ad. - mAdView.loadAd(adRequest); mAdView.setAdListener( new AdListener() { @Override public void onAdLoaded() { final BannerAdSizeInfo sizeInfo = new BannerAdSizeInfo(mAdView); - notifyListeners(BannerAdPluginEvents.SizeChanged.getWebEventName(), sizeInfo); notifyListeners(BannerAdPluginEvents.Loaded.getWebEventName(), emptyObject); super.onAdLoaded(); @@ -293,8 +300,7 @@ public void onAdImpression() { } ); - // Add AdViewLayout top of the WebView - mViewGroup.addView(mAdViewLayout); + mAdView.loadAd(adRequest); }); } } diff --git a/android/src/main/java/com/getcapacitor/community/admob/consent/AdConsentExecutor.java b/android/src/main/java/com/getcapacitor/community/admob/consent/AdConsentExecutor.java index 6c5b438a..b4555c18 100644 --- a/android/src/main/java/com/getcapacitor/community/admob/consent/AdConsentExecutor.java +++ b/android/src/main/java/com/getcapacitor/community/admob/consent/AdConsentExecutor.java @@ -71,7 +71,7 @@ public void requestConsentInfo(final PluginCall call, BiConsumer call.reject(formError.getMessage()) + (formError) -> call.reject(formError.getMessage()) ); } catch (Exception ex) { call.reject(ex.getLocalizedMessage(), ex); @@ -88,7 +88,7 @@ public void showPrivacyOptionsForm(final PluginCall call, BiConsumer - UserMessagingPlatform.showPrivacyOptionsForm(activity, formError -> { + UserMessagingPlatform.showPrivacyOptionsForm(activity, (formError) -> { if (formError != null) { call.reject("Error when show privacy form", formError.getMessage()); } else { @@ -112,7 +112,7 @@ public void showConsentForm(final PluginCall call, BiConsumer ensureConsentInfo(); activity.runOnUiThread(() -> - UserMessagingPlatform.loadAndShowConsentFormIfRequired(activity, formError -> { + UserMessagingPlatform.loadAndShowConsentFormIfRequired(activity, (formError) -> { if (formError != null) { call.reject("Error when show consent form", formError.getMessage()); return; diff --git a/android/src/test/java/com/getcapacitor/community/admob/consent/AdConsentExecutorTest.java b/android/src/test/java/com/getcapacitor/community/admob/consent/AdConsentExecutorTest.java index 91f8989f..e4ddfd92 100644 --- a/android/src/test/java/com/getcapacitor/community/admob/consent/AdConsentExecutorTest.java +++ b/android/src/test/java/com/getcapacitor/community/admob/consent/AdConsentExecutorTest.java @@ -62,7 +62,7 @@ void beforeEach() { adConsentExecutor = new AdConsentExecutor(() -> contextMock, () -> activityMock, notifierMock, LOG_TAG); - doAnswer(invocation -> { + doAnswer((invocation) -> { Runnable runnable = invocation.getArgument(0); runnable.run(); return null; diff --git a/android/src/test/java/com/getcapacitor/community/admob/interstitial/AdInterstitialExecutorTest.java b/android/src/test/java/com/getcapacitor/community/admob/interstitial/AdInterstitialExecutorTest.java index 70ffd497..6447fa1a 100644 --- a/android/src/test/java/com/getcapacitor/community/admob/interstitial/AdInterstitialExecutorTest.java +++ b/android/src/test/java/com/getcapacitor/community/admob/interstitial/AdInterstitialExecutorTest.java @@ -146,7 +146,8 @@ void usesIdHelper() { Runnable uiThreadRunnable = runnableArgumentCaptor.getValue(); uiThreadRunnable.run(); - interstitialAdMockedStatic.verify(() -> InterstitialAd.load(any(), idArgumentCaptor.capture(), adRequestCaptor.capture(), any()) + interstitialAdMockedStatic.verify(() -> + InterstitialAd.load(any(), idArgumentCaptor.capture(), adRequestCaptor.capture(), any()) ); assertEquals(idFromViewHelper, idArgumentCaptor.getValue());