Simon Raes
Simon Raes

Reputation: 461

QuickBlox notifications: push token disappears

I've been working on an Android app that uses QuickBlox for chatting. The chat is working fine, but I am encountering difficulties with the push notifications for logged out users.

The problem is that some devices sometimes stop receiving notifications. It often works fine for a couple of hours, but then suddenly completely stops working for that device. A full manual uninstall + reinstall (which then logs the user in again to QuickBlox + GCM) usually solves the problem.

Looking at the QuickBlox admin panel, I can see that some subscriptions lose their push token.

User 1 registered on device A. Push token has disappeared. https://i.sstatic.net/YUkyw.png

User 1 registered on device B. Push token still present. https://i.sstatic.net/Yu5IQ.png

I register with GCM + QuickBlox push messages once the user is logged in to QuickBlox. This code was largely taken from the QuickBlox chat sample.

ChatService:

private void loginToChat(final Context context, final QBUser user, final QBEntityCallback callback)
{
    this.qbChatService.login(user, new QBEntityCallbackImpl()
    {
        @Override
        public void onSuccess()
        {

            checkPlayServices(context);

            try
            {
                qbChatService.startAutoSendPresence(AUTO_PRESENCE_INTERVAL_IN_SECONDS);
            }
            catch (SmackException.NotLoggedInException e)
            {
                e.printStackTrace();
            }

            if (callback != null)
            {
                callback.onSuccess();
            }
        }

        @Override
        public void onSuccess(Object result, Bundle params)
        {
            super.onSuccess(result, params);

            checkPlayServices(context);
        }

        @Override
        public void onError(List errors)
        {
            if (callback != null)
            {
                callback.onError(errors);
            }
        }
    });
}


private void checkPlayServices(Context context)
{
    // Register with GCM after the session has been created
    PlayServicesHelper playServicesHelper = new PlayServicesHelper(context);
    playServicesHelper.checkPlayServices();
}

PlayServicesHelper:

public class PlayServicesHelper
{
private static final String PROPERTY_APP_VERSION = "appVersion";
private static final String PROPERTY_REG_ID = "registration_id";
private static final String TAG = "PlayServicesHelper";
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

private GoogleCloudMessaging googleCloudMessaging;
private Context activity;
private String regId;

private String projectNumber;

public PlayServicesHelper(Context activity)
{
    this.activity = activity;
    this.projectNumber = activity.getString(R.string.gcm_project_number);
    checkPlayService();
}

private void checkPlayService()
{
    // Check device for Play Services APK. If check succeeds, proceed with
    // GCM registration.
    if (checkPlayServices())
    {
        googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
        regId = getRegistrationId();

        if (regId.isEmpty())
        {
            registerInBackground();
        }
    }
    else
    {
        Log.i(TAG, "No valid Google Play Services APK found.");
    }
}

/**
 * Check the device to make sure it has the Google Play Services APK. If
 * it doesn't, display a dialog that allows users to download the APK from
 * the Google Play Store or enable it in the device's system settings.
 */
public boolean checkPlayServices()
{
    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
    if (resultCode != ConnectionResult.SUCCESS)
    {
        if (GooglePlayServicesUtil.isUserRecoverableError(resultCode))
        {
            GooglePlayServicesUtil.getErrorDialog(resultCode, activity, PLAY_SERVICES_RESOLUTION_REQUEST).show();
        }
        return false;
    }
    return true;
}

/**
 * Gets the current registration ID for application on GCM service.
 * <p/>
 * If result is empty, the app needs to register.
 *
 * @return registration ID, or empty string if there is no existing
 * registration ID.
 */
private String getRegistrationId()
{
    final SharedPreferences prefs = getGCMPreferences();
    String registrationId = prefs.getString(PROPERTY_REG_ID, "");
    if (registrationId.isEmpty())
    {
        Log.i(TAG, "Registration not found.");
        return "";
    }
    // Check if app was updated; if so, it must clear the registration ID
    // since the existing regID is not guaranteed to work with the new
    // app version.
    int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);

    int currentVersion = getAppVersionCode();
    if (registeredVersion != currentVersion)
    {
        Log.i(TAG, "App version changed.");
        return "";
    }
    return registrationId;
}

/**
 * Registers the application with GCM servers asynchronously.
 * <p/>
 * Stores the registration ID and app versionCode in the application's
 * shared preferences.
 */
private void registerInBackground()
{
    new AsyncTask<Void, Void, String>()
    {
        @Override
        protected String doInBackground(Void... params)
        {
            String msg = "";
            try
            {
                if (googleCloudMessaging == null)
                {
                    googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
                }
                regId = googleCloudMessaging.register(projectNumber);
                msg = "Device registered, registration ID=" + regId;

                // You should send the registration ID to your server over HTTP, so it
                // can use GCM/HTTP or CCS to send messages to your app.
                Handler h = new Handler(activity.getMainLooper());
                h.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        subscribeToPushNotifications(regId);
                    }
                });

                // Persist the regID - no need to register again.
                storeRegistrationId(regId);
            }
            catch (IOException ex)
            {
                msg = "Error :" + ex.getMessage();
                // If there is an error, don't just keep trying to register.
                // Require the user to click a button again, or perform
                // exponential back-off.
            }
            return msg;
        }

        @Override
        protected void onPostExecute(String msg)
        {
            Log.i(TAG, msg + "\n");
        }
    }.execute(null, null, null);
}

/**
 * @return Application's {@code SharedPreferences}.
 */
private SharedPreferences getGCMPreferences()
{
    return activity.getSharedPreferences(activity.getPackageName(), Context.MODE_PRIVATE);
}

/**
 * Subscribe to Push Notifications
 *
 * @param regId registration ID
 */
private void subscribeToPushNotifications(String regId)
{
    String deviceId;

    final TelephonyManager mTelephony = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
    if (mTelephony.getDeviceId() != null)
    {
        deviceId = mTelephony.getDeviceId();
    }
    else
    {
        deviceId = Settings.Secure.getString(activity.getContentResolver(), Settings.Secure.ANDROID_ID);
    }

    QBMessages.subscribeToPushNotificationsTask(regId, deviceId, QBEnvironment.PRODUCTION, new QBEntityCallbackImpl<ArrayList<QBSubscription>>()
    {
        @Override
        public void onSuccess(ArrayList<QBSubscription> qbSubscriptions, Bundle bundle)
        {

        }

        @Override
        public void onError(List<String> strings)
        {

        }
    });
}

/**
 * Stores the registration ID and app versionCode in the application's
 * {@code SharedPreferences}.
 *
 * @param regId registration ID
 */
private void storeRegistrationId(String regId)
{
    final SharedPreferences prefs = getGCMPreferences();
    int appVersion = getAppVersionCode();
    SharedPreferences.Editor editor = prefs.edit();
    editor.putString(PROPERTY_REG_ID, regId);
    editor.putInt(PROPERTY_APP_VERSION, appVersion);
    editor.apply();
}

private int getAppVersionCode()
{
    try
    {
        PackageInfo packageInfo = this.activity.getPackageManager().getPackageInfo(this.activity.getPackageName(), 0);
        return packageInfo.versionCode;
    }
    catch (PackageManager.NameNotFoundException e)
    {
        return 0;
    }
}
}

To allow a user to receive notifications, I log him out every time the app goes to the background (using onStop in a BaseActivity) and then log back in when the app resumes (which then again starts the PlayServicesHelper).

ChatService.getInstance().logout(null);

The app also uses Parse (with GCM) for push notifications, but the problem still seemed to occur when I disabled Parse.

Could the problem be caused by having one user registered on multiple devices at the same time? Could the frequent app installs (without manually removing the previous installation) result in this behavior?

Upvotes: 2

Views: 559

Answers (1)

Rubycon
Rubycon

Reputation: 18346

Push token can disappear if 2 users use the same device

for example, User 1 subscribed for pushes on deviceA

Then User 2 subscribed for pushes on deviceA (the same device)

After this only User 2 will be receiving pushes, not User 1

Is there a chance to have such logic in your case?

Upvotes: 0

Related Questions