Suggestions

close search

Android Push notification

When a Stringee call is made, Stringee Server sends a push message to the receiving client. If the client is already connected, the incoming call is delivered through StringeeConnectionListener.onIncomingCall or onIncomingCall2, and the pushed message can be ignored. If the client is disconnected or the app process is not running, use the push message to reconnect; the incoming call is then delivered through the same listener.

This tutorial walks through integrating Firebase Cloud Messaging (FCM), registering the device token with Stringee, and displaying an incoming-call notification.

1. Create an API project

Create a Firebase project in the Firebase console, add your Android application, and download google-services.json.

2. Set up your project for push notification

In Firebase Project Settings -> General, copy the Firebase project ID.

On Stringee Dashboard, go to Push Notification, select your Stringee project, and create or update the Android application with the Firebase project ID and Android package name.

In Firebase Project Settings -> Service accounts, generate a Firebase Admin SDK private key. Upload the downloaded JSON service-account key to the Android push application on Stringee Dashboard. Keep this file private; never bundle it in the mobile application or commit it to source control.

3. Set up Firebase and add FCM SDK to your app

Copy google-services.json to the app module directory, normally app/google-services.json.

For a project using the Gradle plugins DSL, add the Google Services plugin to the project-level build.gradle:

plugins {
    id 'com.google.gms.google-services' version '4.5.0' apply false
}

Apply it in the app-level build.gradle:

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services'
}

Add Firebase Messaging by using the Firebase BoM:

dependencies {
    implementation platform('com.google.firebase:firebase-bom:34.17.0')
    implementation 'com.google.firebase:firebase-messaging'
}

4. Device registration token

Retrieve the current registration token

The FCM SDK generates a token for each app installation. Retrieve it asynchronously:

FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
    if (!task.isSuccessful()) {
        return;
    }

    String token = task.getResult();
    registerPushToken(token);
});
Register the registration token with Stringee Server

Register the token only after StringeeClient is connected:

private void registerPushToken(String token) {
    if (stringeeClient == null || !stringeeClient.isConnected()) {
        return;
    }

    stringeeClient.registerPushToken(token, new StatusListener() {
        @Override
        public void onSuccess() {
        }

        @Override
        public void onError(StringeeError error) {
        }
    });
}

Persist the current token if it must be registered later, after the client connects.

5. Receive messages

Create a service that extends FirebaseMessagingService and declare it in AndroidManifest.xml:

<service
    android:name=".MyFirebaseMessagingService"
    android:enabled="true"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
Monitor new token generation

FCM calls onNewToken when the initial token is created or an existing token changes. Register the replacement token with Stringee again:

@Override
public void onNewToken(@NonNull String token) {
    savePushToken(token);
    registerPushToken(token);
}

FirebaseMessagingService may run when your Activity is not alive. Store or obtain the connected StringeeClient through your application-level owner instead of holding an Activity reference in the service.

Override onMessageReceived

Check that the data message comes from Stringee. When the client is already connected, wait for the Stringee incoming-call listener. Otherwise, reconnect with a valid access token. Display an incoming-call notification for a started event while the connection is being restored.

@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
    Map<String, String> data = remoteMessage.getData();
    if (!data.containsKey("stringeePushNotification")) {
        return;
    }

    String payload = data.get("data");
    if (payload == null) {
        return;
    }

    try {
        JSONObject callData = new JSONObject(payload);
        String callStatus = callData.optString("callStatus");
        if ("started".equals(callStatus)) {
            showIncomingCallNotification(callData);
            connectStringeeIfNeeded();
        } else if ("answered".equals(callStatus) || "ended".equals(callStatus)) {
            cancelIncomingCallNotification(callData.optString("callId"));
        }
    } catch (JSONException error) {
        Log.e("StringeePush", "Invalid Stringee push payload", error);
    }
}
Display an incoming-call notification

A Stringee FCM data payload has the following shape. All top-level values delivered by RemoteMessage.getData() are strings; the value of data is a JSON-encoded string.

{
  "stringeePushNotification": "1.0",
  "type": "CALL_EVENT",
  "data": "{\"callId\":\"CALL_ID\",\"serial\":1,\"callStatus\":\"started\",\"from\":{\"number\":\"user2\",\"alias\":\"User 2\"},\"to\":{\"number\":\"user1\",\"alias\":\"User 1\"},\"projectId\":1234}"
}

Declare notification permissions in AndroidManifest.xml:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />

On Android 13 or later, request POST_NOTIFICATIONS at runtime. Full-screen intents are intended only for urgent calling or alarm experiences. On recent Android versions, users can control this permission, so check NotificationManagerCompat.from(context).canUseFullScreenIntent() and provide a normal high-priority call notification as a fallback.

Create a high-importance channel and use NotificationCompat.CallStyle:

private static final String CALL_CHANNEL_ID = "incoming_calls";

private void createCallNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
            CALL_CHANNEL_ID,
            "Incoming calls",
            NotificationManager.IMPORTANCE_HIGH
        );
        channel.setDescription("Incoming Stringee calls");
        getSystemService(NotificationManager.class).createNotificationChannel(channel);
    }
}

private PendingIntent createCallActionPendingIntent(String callId, String action) {
    Intent intent = new Intent(this, CallActionReceiver.class)
        .setAction(action)
        .putExtra("callId", callId);
    return PendingIntent.getBroadcast(
        this,
        (callId + action).hashCode(),
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
    );
}

private void showIncomingCallNotification(JSONObject callData) {
    createCallNotificationChannel();

    String callId = callData.optString("callId");
    JSONObject from = callData.optJSONObject("from");
    String caller = from != null ? from.optString("alias", from.optString("number")) : "Unknown caller";

    Intent openIntent = new Intent(this, IncomingCallActivity.class)
        .putExtra("callId", callId);
    PendingIntent fullScreenIntent = PendingIntent.getActivity(
        this,
        callId.hashCode(),
        openIntent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
    );

    PendingIntent answerIntent = createCallActionPendingIntent(callId, "ANSWER");
    PendingIntent declineIntent = createCallActionPendingIntent(callId, "DECLINE");
    Person person = new Person.Builder().setName(caller).setImportant(true).build();

    Notification notification = new NotificationCompat.Builder(this, CALL_CHANNEL_ID)
        .setSmallIcon(R.drawable.ic_call)
        .setContentTitle("Incoming call")
        .setContentText(caller)
        .setCategory(NotificationCompat.CATEGORY_CALL)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setOngoing(true)
        .setAutoCancel(false)
        .setFullScreenIntent(fullScreenIntent, true)
        .setStyle(NotificationCompat.CallStyle.forIncomingCall(person, declineIntent, answerIntent))
        .build();

    NotificationManagerCompat.from(this).notify(callId.hashCode(), notification);
}

Route answer and decline actions through a BroadcastReceiver or service. Reconnect and obtain the actual StringeeCall/StringeeCall2 object before calling answer or reject; never treat the FCM payload itself as the call object.

6. Unregister the registration token

When the signed-in user no longer wants to receive Stringee push notifications, unregister the current token before disconnecting:

stringeeClient.unregisterPushToken(token, new StatusListener() {
    @Override
    public void onSuccess() {
    }

    @Override
    public void onError(StringeeError error) {
    }
});

You can view the completed Android sample on GitHub: Sample