Troubleshooting Android

Common issues on Android.

👍

Activate logging when troubleshooting

You can activate WonderPush logs by adding a single line in your build config: buildConfigField 'boolean', 'WONDERPUSH_LOGGING', 'true'

Our logs report pretty much the entire activity of our SDK. They are all prefixed with e with the l so they're easy to filter in and out.

Don't forget to turn them off in your release build.

Make sure you use minimum required versions

If you keep your application up-to-date with the latest Android Studio, Gradle and Gradle plugin, then you are probably already complying with these requirements, but let's be explicit to ensure success.

  • Make sure to use the latest targetSdkVersion, 31 minimum, per Google Play requirement
  • Make sure to use the latest compileSdkVersion, 31 minimum.
  • If you are still not using legacy support libraries instead of AndroidX, make sure you use appropriate version, matching compileSdkVersion.
  • Make sure to use minSdkVersion 21 minimum.

This translates to the following in your app/build.gradle file:

android {
    # Use 30 minimum.
    # Using the latest is encouraged by Android Studio
    # Cannot be lower than targetSdkVersion
    compileSdkVersion 31
    
    defaultConfig {
        # Use 31 minimum per Google Play requirement: https://support.google.com/googleplay/android-developer/answer/11926878
        # using the latest is encouraged by Android Studio
        targetSdkVersion 31
        
        minSdkVersion 21
    }
}

“uses-sdk:minSdkVersion 16 cannot be smaller than version 21” error message

We have raised the required minSdkVersion to 21, released in 2014 and covers 95% of Android devices.

Make sure your application uses at least 21 in your app/build.gradle, otherwise you'll see the following build error:

Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [com.wonderpush:wonderpush-android-sdk:4.0.1] [...]/.gradle/caches/transforms-2/files-2.1/110f18d74c064672b3bb9b02e46b3f51/jetified-wonderpush-android-sdk-4.0.1/AndroidManifest.xml as the library might be using APIs not available in 16
	Suggestion: use a compatible library with a minSdk of at most 16,
		or increase this project's minSdk version to at least 21,
		or use tools:overrideLibrary="com.wonderpush.sdk" to force usage (may lead to runtime failures)

Trouble receiving notifications

First thing, make sure that you are in the targeted audience by double checking that your installation is in the chosen segment. Look at the installation's tags, properties and events from the dashboard.

Make sure you don't have tons of notifications already displayed, as some devices will not show new notifications.

Freshly started emulator

If you have started your emulator from a save state, FCM somehow does not restores it's connection to Google's backend. The result is that you do not receive notifications for ~15 minutes.

You can fix it by running the following command:

adb shell su root pkill gms

If the above does not fix the issue, you can force your emulator to perform a cold boot by finding your device in the list of the Device Manager tool window of Android Studio, tuck on on right side of the window, clicking the ⋮ icon to reveal a menu and choose Cold Boot Now.

After stopping the app in Android Studio

When developing you might click on the red square Stop button in Android Studio. This kills the application.
Once the app is killed, you will see a log like this in logcat: W/GCM: broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE flg=0x10000000 pkg=com.your.package (has extras) }.
This is consistent with Force quitting the application and it is a feature of GCM/FCM.

Simply restart your application, then swipe it up in the recent tasks.
You can make sure your application is no longer running by using the following command:

adb shell ps | grep -F com.your.package

If necessary, after closing all your activities, you can stop your app cleanly by using the following command:

adb shell am kill com.your.package

Huawei devices

If you are testing push notifications with Huawei devices, make sure you have added Huawei Mobile Support or you will likely not receive notifications.

Recently upgraded to our Android SDK v4?

Do not forget to add the FCM compatibility module when upgrading to our Android SDK v4.
Otherwise you will see the following error in your logs:

E/WonderPush.Push: Cannot refresh push subscription, no push service available

See the Upgrading to Android SDK v4 guide for resolution.

Network firewalls

If your device is connected to the Internet via an enterprise network, chances are you are behind a Firewall. Firebase Cloud Messaging, the technology behind Android push notifications, requires ports 5228, 5229 and 5230 to be open for notifications to be delivered. More information.

Power management

In low battery or battery saver modes, notifications can be delayed.

Devices with stamina or ultra power mode enabled drop push notifications altogether to improve battery life even further. Please disable such mode if you want to receive notifications.

The app must not have been force quit or killed.
Note that some devices automatically kill applictions after some time spent in the background as a mean to extend battery life. This can be easily checked if you see W/GCM: broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE flg=0x10000000 pkg=com.your.package (has extras) } logs in logcat when the device receives a push notification. This indicates that FCM will not wake the app up to let it display the appropriate notification, and thus while delivered properly, the device drops the notification.

Adaptive battery may delay notifications for some apps. You can disable it in the Settings / Battery / Adaptive Battery screen.

App Standby Buckets further affect the application's ability to consume power. In the Settings / Developer Options / Apps (before last section) / Standby apps screen, you can read the current app's state. Read more about app standby buckets on App Standby Buckets and Power management restrictions in the Android documentation.
You can explicitly exempt an application from the Settings / Apps & notifications / Advanced / Special app access / Battery optimization screen, select All apps in the dropdown above the list, click on your app, and choose Don't optimize in the popup.
The most effective way to avoid the restrictive buckets is simply to open the app often. Note that if you recently opened the application to check something like whether you actually subscribed to that event you expected a notification for, you have hence influenced the app standby bucket and just gave it more quota on power usage.

Multiple FirebaseMessagingService

If you have multiple dependencies that implement push notifications (even help desks) or background cloud messaging (typically for synchronization purposes), chances are that your application has multiple registered FirebaseMessagingService services. Unfortunately only one of these will be used and you will not see any compilation warnings.

In order to check if this is the case, you can open the app/build/intermediates/merged_manifests/debug/AndroidManifest.xml file for mentions of <action android:name="com.google.firebase.MESSAGING_EVENT" />.
You should only see the following entries:

<service android:name="com.wonderpush.sdk.push.fcm.FirebaseMessagingService" android:exported="false">
  <intent-filter>
    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
  </intent-filter>
</service>

<service android:name="com.google.firebase.messaging.FirebaseMessagingService" android:directBootAware="true" android:exported="false">
  <intent-filter android:priority="-500">
    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
  </intent-filter>
</service>

The easy solution is to only use WonderPush as push notification provider. Make sure to remove your old push provider.

If you cannot or do not wish to remove the other dependency, like a help desk dependency that helps you chat with your end-users.
You will then need to implement your own FirebaseMessagingService and in turn call onNewToken(String token) and onMessageReceived(RemoteMessage message) on our class com.wonderpush.sdk.push.fcm.FirebaseMessagingService. Do not forget to do likewise for the other dependency too.

Here is an example code:

<!-- Add the following inside your <application> tag -->
<service android:name=".WrapperFirebaseMessagingService" android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
package COM.YOUR.PACKAGE; // TODO: Put your application package here

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.util.Log;

import androidx.annotation.NonNull;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

/**
 * This class wraps multiple FirebaseMessagingService implementations
 * and makes sure each will be called appropriately.
 */
public class WrapperFirebaseMessagingService extends FirebaseMessagingService {

    private List<FirebaseMessagingService> messagingServices = new ArrayList<>();

    public WrapperFirebaseMessagingService() {
        // TODO: Add all the FirebaseMessagingServices you need here
        messagingServices.add(new com.wonderpush.sdk.push.fcm.FirebaseMessagingService());
    }

    //
    // Forward FirebaseMessagingService methods
    //

    // WonderPush needs this method to be forwarded
    @Override
    public void onNewToken(String s) {
        forward(service -> {
            service.onNewToken(s);
        });
    }

    // WonderPush needs this method to be forwarded
    @Override
    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
        forward(service -> {
            service.onMessageReceived(remoteMessage);
        });
    }

    @Override
    public void onDeletedMessages() {
        forward(service -> {
            service.onDeletedMessages();
        });
    }

    @Override
    public void onMessageSent(@NonNull String s) {
        forward(service -> {
            service.onMessageSent(s);
        });
    }

    @Override
    public void onSendError(@NonNull String s, @NonNull Exception e) {
        forward(service -> {
            service.onSendError(s, e);
        });
    }

    //
    // Forward Service methods
    //

    @Override
    public void onCreate() {
        forward(service -> {
            service.onCreate();
        });
    }

    @Override
    public void onStart(Intent intent, int startId) {
        forward(service -> {
            service.onStart(intent, startId);
        });
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        forward(service -> {
            service.onConfigurationChanged(newConfig);
        });
    }

    @Override
    public void onLowMemory() {
        forward(service -> {
            service.onLowMemory();
        });
    }

    @Override
    public void onTrimMemory(int level) {
        forward(service -> {
            service.onTrimMemory(level);
        });
    }

    @Override
    public boolean onUnbind(Intent intent) {
        final AtomicBoolean rtn = new AtomicBoolean(false);
        forward(service -> {
            if (service.onUnbind(intent)) {
                rtn.set(true);
            }
        });
        return rtn.get();
    }

    @Override
    public void onRebind(Intent intent) {
        forward(service -> {
            service.onRebind(intent);
        });
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        forward(service -> {
            service.onTaskRemoved(rootIntent);
        });
    }

    //
    // Wrapping code
    //

    private void forward(Forwarder action) {
        for (FirebaseMessagingService service : messagingServices) {
            action.run(service);
        }
    }

    interface Forwarder {
        void run(FirebaseMessagingService service);
    }

    // Give the context to wrapped services or they won't be able to call getApplicationContext() and the like,
    // and they will hence likely fail.
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        // Accessing protected method Landroid/app/Service;->attachBaseContext(Landroid/content/Context;)V,public-api,sdk,system-api,test-api
        Method method;
        try {
            method = Service.class.getDeclaredMethod("attachBaseContext", Context.class);
        } catch (NoSuchMethodException ex) {
            Log.e(this.getClass().getSimpleName(), "Failed to reflect Service.attachBaseContext", ex);
            return;
        }

        boolean wasAccessible = method.isAccessible();
        if (!wasAccessible) method.setAccessible(true);

        // Forward attachBaseContext to the wrapped services too
        forward(service -> {
            try {
                method.invoke(service, newBase);
            } catch (IllegalAccessException ex) {
                Log.e(this.getClass().getSimpleName(), "Failed to invoke Service.attachBaseContext for " + service, ex);
            } catch (InvocationTargetException ex) {
                Log.e(this.getClass().getSimpleName(), "Got an error while invoking Service.attachBaseContext for " + service, ex);
            }
        });

        if (!wasAccessible) method.setAccessible(false);
    }

}
dependencies {
  // You may need to add the following dependencies
  //implementation 'com.google.firebase:firebase-messaging'
  //implementation 'com.wonderpush:wonderpush-android-sdk-fcm:1.0.5'
}

Build error mentioning AndroidX

You have recently upgraded our Android SDK to v4 but your application does not use AndroidX and you get the following Gradle build errors:

This project uses AndroidX dependencies, but the 'android.useAndroidX' property is not enabled. Set this property to true in the gradle.properties file and retry.
The following AndroidX dependencies are detected: androidx.collection:collection:1.0.0, androidx.cardview:cardview:1.0.0, androidx.versionedparcelable:versionedparcelable:1.0.0, androidx.core:core:1.0.0, androidx.localbroadcastmanager:localbroadcastmanager:1.0.0, androidx.lifecycle:lifecycle-common:2.0.0, androidx.arch.core:core-common:2.0.0, androidx.constraintlayout:constraintlayout:1.1.3, androidx.constraintlayout:constraintlayout-solver:1.1.3, androidx.lifecycle:lifecycle-runtime:2.0.0, androidx.annotation:annotation:1.0.0

Our SDK now depends on AndroidX to benefit from more modular dependencies.
Simply follow Android's Migrating to AndroidX guide.
Migrating is as easy as clicking on the Refactor > Migrate to AndroidX menu entry in Android Studio.

All com.android.support libraries must use the exact same version specification

📘

You are still using our Android SDK v3.x. We have released our Android SDK v4.
Please upgrade using our Upgrading to Android SDK v4 guide.

If you see the error All com.android.support libraries must use the exact same version specification, add the following in your app/build.gradle file:

dependencies {
    # Make sure you use a version matching your compileSdkVersion, 26.0.2 minimum
    # Use the highest version you see in the error message
    implementation 'com.android.support:support-v4:+'
}

Duplicate class after upgrading to SDK v4.2.0+

If you see one or multiple errors mentioning something like:

Duplicate class com.google.android.play.core.common.IntentSenderForResultStarter found in modules jetified-core-1.10.0-runtime (com.google.android.play:core:1.10.0) and jetified-core-common-2.0.0-runtime (com.google.android.play:core-common:2.0.0)

You likely are still using the non modular version of the Google Play Services library. Look for dependencies starting with com.google.android.play: and using the major version 1.
You should update your dependency as outlined in this article: Migration from the Play Core Java and Kotlin Library

If you have no such direct dependency, run the following command to discover which of your direct dependency is indirectly depending on the offending module:

./gradlew --no-daemon app:dependencies --configuration releaseRuntimeClasspath

It outputs a dependency tree that will help you walk up to one of your direct dependencies. Check if an update exists for it.
If not you can still do things like or excluding an indirect dependency or forcing the resolved version of a library. Proceed with caution, it's not always easy to do. Here are examples:

// Excluding an indirect dependency
implementation('group.of.offending.dependency:module.of.offending.dependency:1.+') {
    exclude group: 'com.google.android.play', module: 'core'
}
// Overriding the resolved dependency version
configurations.all {
  resolutionStrategy { 
    force 'com.google.android.play:core-common:2.0.0'
    force 'com.google.android.play:review:2.0.0'
  }
}

Could not initialize WonderPush

If you see the following log:

WonderPush: Could not initialize WonderPush using the initializer class, BuildConfig options or manifest <meta-data> options!
WonderPush: No BuildConfig class found. You probably need to give the value of your gradle defaultConfig.applicationId as the a "wonderpush_buildConfigPackage" string resource or a "com.wonderpush.sdk.buildConfigPackage" manifest <meta-data>.

then you will need to tell the SDK where to find the BuildConfig class.

Firebase fails to initialize. My installation is opt-out.

If you see one of the following errors:

java.lang.IllegalArgumentException: Please set your project ID. A valid Firebase project ID is required to communicate with Firebase server APIs: It identifies your project with Google.
Could not get Firebase InstanceId
java.io.IOException: FIS_AUTH_ERROR

You must use the WonderPush SDK FCM module version 1.0.1 at least.

Edit your app/build.gradle to add:

dependencies {
  implementation 'com.wonderpush:wonderpush-android-sdk-fcm:[1.0.1,2)'
}

Huawei: My installation is opt-out

If you see the following error in your device logs:

E WonderPush.Push.HCM.HCMPushService: Could not get HMS InstanceId
E WonderPush.Push.HCM.HCMPushService: com.huawei.hms.common.ApiException: 907135701: scope list empty

then you probably have a mismatch between the signing configuration and the SHA-256 certificate fingerprint configured in AppGallery Connect.

Check your app/build.gradle file:

android {
    signingConfigs {
        mySignConfig { // <- note the actual name you have, and use it below
            // [...]
        }
    }

    // Make sure to setup the signingConfig in all buildTypes.
    // You are most likely missing the debug build type.
    buildTypes {
        debug {
            signingConfig signingConfigs.mySignConfig // <- use the actual name of one of your signingConfigs
            // [...]
        }
        release {
            signingConfig signingConfigs.mySignConfig // <- use the actual name of one of your signingConfigs
            // [...]
        }
    }
}

You can also add more SHA-256 certificate fingerprints in AppGallery Connect. Follow Huawei Push Kit / Preparations / 1.3 Generating a Signing Certificate Fingerprint documentation on how to proceed.

Installations have no push token, in my application uses MultiDex

You should see an error in the logs saying that we could not initialize Firebase due to the following error:

java.lang.NoClassDefFoundError: class com.google.firebase.iid.* is not an found

Then you are probably developing a MultiDex-enabled application.
This is due to the Firebase classes not being included in the primary dex.

The simplest solution we found is to bump our dependency of the Firebase Cloud Messaging dependency.
In your app/build.gradle file, simply add this new dependency new to our SDK's FCM module:

implementation 'com.google.firebase:firebase-messaging:20.3.0'

I do not see any logs in logcat

If you want to see more logs from WonderPush, you can call the WonderPush.setLogging(true); method, or better, add this buildConfigField to grab the logs of the SDK before your Application class is ever run.

Sometimes your project can be built with extra "optimizations", that remove all logging, hence your application is completely mute in logcat. We do not recommend using such a configuration. This happens in builds using minifyEnabled true, where ProGuard is used.

Check that you do not have the following ProGuard rules in your project, typically in your app/proguard-rules.txt:

# This deactivates all logging
-assumenosideeffects class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
    public static *** wtf(...);
    public static *** println(...);
}

# The following seems simpler, but has other dangerously side effects too.
# Remove this immediately if you ever find it in your codebase!
-assumenosideeffects class android.util.Log {
    public *;
}

If you find these rules, remove them and the logs will start flowing again.

This can happen too if you use the proguard-android-optimize.txt rules, instead of the regular proguard-android.txt ones, as they add the above -assumenosideeffects rules.

Leaked GCP API Keys

If you have this kind of message, it's because when the WonderPush SDK calls Firebase to get a push token, Google notes the API Key used, and after running an analysis, considers that it has leaked.

814

What should I do?
You should follow the recommendations in the linked Google Help Center article named Remediation for Exposed GCP API keys.

  1. Make sure to use your own Firebase account.
  2. Generate a new API Key
  3. Apply restrictions on the new API Key
  4. Edit your google-services.json file to use the new API Key. This step must be done each time you download the google-services.json file as Firebase will not update it server-side. Note that you must not delete the previous key or your current application installations on your users' devices will stop working.

Note that you may still see the warning after you have restricted the API Key.
Here are the explanations from a Google Security expert.

758

https://www.googlecloudcommunity.com/gc/Security/Leaked-GCP-API-Keys/m-p/181838/highlight/true#M136

Here are recommendations on Securing an API Key.

It's interesting to note that Firebase declares that the API Key is a public information in the first place, here and there.