Skip to content

Commit

Permalink
Added AppStateEventNotifier. This replaces OnApplicationPause for App…
Browse files Browse the repository at this point in the history
…OpenAds.

PiperOrigin-RevId: 455262379
  • Loading branch information
Mobile Ads Developer Relations authored and maddevrelgithubbot committed Jun 16, 2022
1 parent 6613d76 commit 95182ef
Show file tree
Hide file tree
Showing 16 changed files with 454 additions and 42 deletions.
12 changes: 12 additions & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
Google Mobile Ads Unity Plugin Change Log

**************
Version Next
**************

Plugin:
- Added AppStateEventNotifier as a better option to OnApplicationPause for app open ads.

Built and tested with:
- Google Mobile Ads Android SDK 21.0.0.
- Google Mobile Ads iOS SDK 9.0.0
- External Dependency Manager for Unity 1.2.171.

**************
Version 7.0.2
**************
Expand Down
99 changes: 60 additions & 39 deletions samples/HelloWorld/Assets/Scripts/GoogleAdMobController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

public class GoogleAdMobController : MonoBehaviour
{
private readonly TimeSpan APPOPEN_TIMEOUT = TimeSpan.FromHours(4);
private DateTime appOpenExpireTime;
private AppOpenAd appOpenAd;
private BannerView bannerView;
private InterstitialAd interstitialAd;
Expand Down Expand Up @@ -50,6 +52,9 @@ public void Start()

// Initialize the Google Mobile Ads SDK.
MobileAds.Initialize(HandleInitCompleteAction);

// Listen to application foreground / background events.
AppStateEventNotifier.AppStateChanged += OnAppStateChanged;
}

private void HandleInitCompleteAction(InitializationStatus initstatus)
Expand Down Expand Up @@ -93,15 +98,6 @@ private AdRequest CreateAdRequest()
.Build();
}

public void OnApplicationPause(bool paused)
{
// Display the app open ad when the app is foregrounded.
if (!paused)
{
ShowAppOpenAd();
}
}

#endregion

#region BANNER ADS
Expand Down Expand Up @@ -417,6 +413,31 @@ public void ShowRewardedInterstitialAd()

#region APPOPEN ADS

public bool IsAppOpenAdAvailable
{
get
{
return (!isShowingAppOpenAd
&& appOpenAd != null
&& DateTime.Now < appOpenExpireTime);
}
}

public void OnAppStateChanged(AppState state)
{
// Display the app open ad when the app is foregrounded.
UnityEngine.Debug.Log("App State is " + state);

// OnAppStateChanged is not guaranteed to execute on the Unity UI thread.
MobileAdsEventExecutor.ExecuteInUpdate(() =>
{
if (state == AppState.Foreground)
{
ShowAppOpenAd();
}
});
}

public void RequestAndLoadAppOpenAd()
{
PrintStatus("Requesting App Open ad.");
Expand All @@ -430,26 +451,28 @@ public void RequestAndLoadAppOpenAd()
string adUnitId = "unexpected_platform";
#endif
// create new app open ad instance
AppOpenAd.LoadAd(adUnitId, ScreenOrientation.Portrait, CreateAdRequest(), (appOpenAd, error) =>
{
if (error != null)
{
PrintStatus("App Open ad failed to load with error: " + error);
return;
}

PrintStatus("App Open ad loaded. Please background the app and return.");
this.appOpenAd = appOpenAd;
});
AppOpenAd.LoadAd(adUnitId,
ScreenOrientation.Portrait,
CreateAdRequest(),
OnAppOpenAdLoad);
}

public void ShowAppOpenAd()
private void OnAppOpenAdLoad(AppOpenAd ad, AdFailedToLoadEventArgs error)
{
if (isShowingAppOpenAd)
if (error != null)
{
PrintStatus("App Open ad failed to load with error: " + error);
return;
}
if (appOpenAd == null)

PrintStatus("App Open ad loaded. Please background the app and return.");
this.appOpenAd = ad;
this.appOpenExpireTime = DateTime.Now + APPOPEN_TIMEOUT;
}

public void ShowAppOpenAd()
{
if (!IsAppOpenAdAvailable)
{
return;
}
Expand All @@ -459,31 +482,26 @@ public void ShowAppOpenAd()
{
PrintStatus("App Open ad dismissed.");
isShowingAppOpenAd = false;
MobileAdsEventExecutor.ExecuteInUpdate(() => {
if (this.appOpenAd != null)
{
this.appOpenAd.Destroy();
this.appOpenAd = null;
}
});
if (this.appOpenAd != null)
{
this.appOpenAd.Destroy();
this.appOpenAd = null;
}
};
this.appOpenAd.OnAdFailedToPresentFullScreenContent += (sender, args) =>
{
PrintStatus("App Open ad failed to present with error: " + args.AdError.GetMessage());

isShowingAppOpenAd = false;
MobileAdsEventExecutor.ExecuteInUpdate(() => {
if (this.appOpenAd != null)
{
this.appOpenAd.Destroy();
this.appOpenAd = null;
}
});
if (this.appOpenAd != null)
{
this.appOpenAd.Destroy();
this.appOpenAd = null;
}
};
this.appOpenAd.OnAdDidPresentFullScreenContent += (sender, args) =>
{
PrintStatus("App Open ad opened.");
isShowingAppOpenAd = true;
};
this.appOpenAd.OnAdDidRecordImpression += (sender, args) =>
{
Expand All @@ -497,6 +515,8 @@ public void ShowAppOpenAd()
args.AdValue.Value);
PrintStatus(msg);
};

isShowingAppOpenAd = true;
appOpenAd.Show();
}

Expand Down Expand Up @@ -532,7 +552,8 @@ public void OpenAdInspector()
private void PrintStatus(string message)
{
Debug.Log(message);
MobileAdsEventExecutor.ExecuteInUpdate(() => {
MobileAdsEventExecutor.ExecuteInUpdate(() =>
{
statusText.text = message;
});
}
Expand Down
3 changes: 2 additions & 1 deletion source/android-library/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.gms:play-services-ads:21.0.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.1'
implementation 'androidx.lifecycle:lifecycle-process:2.4.1'
}

task clearAar(type: Delete) {
delete 'build/libs/googlemobileads-unity.aar'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2022 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.unity.ads;

/**
* An interface form of {@link UnityAppStateEventCallback} that can be implemented via {@code
* AndroidJavaProxy} in Unity to receive ad events synchronously.
*/
public interface UnityAppStateEventCallback {
void onAppStateChanged(boolean isBackground);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 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.unity.ads;

import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;

/**
* Proxy for the ProcessLifecycleOwner for the Unity AppStateEventNotifier.
*/
public class UnityAppStateEventNotifier implements DefaultLifecycleObserver {

/**
* The {@code Activity} on which the app open ad will display.
*/
private final Activity activity;

/**
* A callback implemented in Unity via {@code AndroidJavaProxy} to receive lifecycle events.
*/
private final UnityAppStateEventCallback callback;

public UnityAppStateEventNotifier(Activity activity, UnityAppStateEventCallback callback) {
this.activity = activity;
this.callback = callback;
}

/** Attaches as an observer of the ProcessLifecycleOwner. */
public void startListening() {
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
ProcessLifecycleOwner.get()
.getLifecycle()
.addObserver(UnityAppStateEventNotifier.this);
}
});
}

/** Detatches as an observer of the ProcessLifecycleOwner. */
public void stopListening() {
activity.runOnUiThread(
new Runnable() {
@Override
public void run() {
ProcessLifecycleOwner.get()
.getLifecycle()
.removeObserver(UnityAppStateEventNotifier.this);
}
});
}

/** DefaultLifecycleObserver implementation. */

@Override
public void onStart(@NonNull LifecycleOwner owner) {
callback.onAppStateChanged(/*isBackground=*/ false);
}

@Override
public void onStop(@NonNull LifecycleOwner owner) {
callback.onAppStateChanged(/*isBackground=*/ true);
}

@Override
public void onCreate(@NonNull LifecycleOwner owner) {
}

@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
}

@Override
public void onResume(@NonNull LifecycleOwner owner) {
}

@Override
public void onPause(@NonNull LifecycleOwner owner) {
}

}
6 changes: 6 additions & 0 deletions source/android-library/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
48 changes: 48 additions & 0 deletions source/plugin/Assets/GoogleMobileAds/Api/AppStateEventNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (C) 2022 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.

using System;
using GoogleMobileAds.Common;

namespace GoogleMobileAds.Api
{
/// <summary>
/// Notifies changes in app foreground/background.
/// Subscribe to AppStateChanged to get notified of changes in app state.
/// </summary>
public class AppStateEventNotifier
{
/// <summary>
/// Raised when the app enters the background/foreground.
/// </summary>
public static event Action<AppState> AppStateChanged
{
add
{
client.AppStateChanged += value;
}
remove
{
client.AppStateChanged -= value;
}
}

private static IAppStateEventClient client;

static AppStateEventNotifier()
{
client = MobileAds.GetClientFactory().BuildAppStateEventClient();
}
}
}
Loading

0 comments on commit 95182ef

Please sign in to comment.