Alberto
Alberto

Reputation: 61

How to refresh registration token in GCM that expired after a while?

this is my first question in here, sorry in advance if something is not ok in my question!

I'm currently using Xamarin in Visual Studio 2013 on a MacBook, I created a PCL project and I created all the things necessary to make the push notifications work for iOS and Android. Using a simulator and also a real device I can achieve both with a server side program where I use the device token created from the client side. Everything worked fine for a couple of hours but after a while both the tokens were expired. I know that because this is the response I gave from the server side.

Device RegistrationId Expired: {e_bSR1SFBzg:APA91bHEQUEFvjn_mblu26MeKhAkcKRtXRLtaHOKYIsDTMDvJT3jmBDb4O6-hhu00ZhEcuAkpfaK-GKicbH1o5gtr3J9Sj9jyf-DVuikTd6DOBM0Tw70KFNcf3a2arMVLlHOy5QctQSn}

And also if I try it from that link http://1-dot-sigma-freedom-752.appspot.com/gcmpusher.jsp

To make it works again I deleted the app from the device and also from the emulator and after a restart a new device token was created and it worked for a while, after a couple of hours same problem. My doubt is about the

InstanceIDListenerService

file that maybe is not configured in the good way. Below part of my code, so if someone can give me any advice, since I am facing this problem for the last two days and I don't know what to do anymore.

My AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="MY.PACKAGE.NAME" android:installLocation="auto" android:versionCode="1" android:versionName="1.0.0">
      <uses-sdk android:minSdkVersion="14" />
      <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
      <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
      <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
      <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.WAKE_LOCK" />
      <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
      <uses-permission android:name="MY.PACKAGE.NAME.permission.C2D_MESSAGE" />
      <permission android:name="MY.PACKAGE.NAME.permission.C2D_MESSAGE" android:protectionLevel="signature" />
      <!--<application android:label="TruckMe" android:icon="@drawable/ic_launcher">-->
      <application android:label="TruckMe" android:icon="@drawable/ic_launcher">
            <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="KEY_FOR-MAPS" />
            <receiver android:name="com.google.android.gms.gcm.GcmReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
                  <intent-filter>
                        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                        <category android:name="MY.PACKAGE.NAME" />
                  </intent-filter>
            </receiver>
      </application>
</manifest>

Where of course I subsituted my real package name with that word and same thing for the Api_KEY. The RegistrationIntentService.cs file:

using System;
using Android.App;
using Android.Content;
using Android.Util;
using Android.Gms.Gcm;
using Android.Gms.Gcm.Iid;


namespace TruckMe.Droid.Infrastructure.Notifications
{
    [Service(Exported = false)]
    public class RegistrationIntentService : IntentService
    {
        private const string SENDER_ID = "my_sender_num";
        static object locker = new object();

        public RegistrationIntentService() : base("RegistrationIntentService") { }

        protected override void OnHandleIntent(Intent intent)
        {
            try
            {
                Log.Info("RegistrationIntentService", "Calling InstanceID.GetToken");
                lock (locker)
                {
                    // Initially this call goes out to the network to retrieve the token, subsequent calls are local.
                    var instanceID = InstanceID.GetInstance(this);

                    // See https://developers.google.com/cloud-messaging/android/start for details on this file.
                    var token = instanceID.GetToken(SENDER_ID, GoogleCloudMessaging.InstanceIdScope, null);

                    //Log.Info("RegistrationIntentService", "GCM Registration Token: " + token);
                    Console.WriteLine("RegistrationIntentService - GCM Token: " + token);
                    SendRegistrationToAppServer(token);
                    Subscribe(token);
                    //unSubscribe(token);
                }
            }
            catch (Exception ex)
            {
                Log.Debug("RegistrationIntentService", "Failed to get a registration token. Ex: " + ex.Message);
                return;
            }
        }

        void SendRegistrationToAppServer(string token)
        {
            // Add custom implementation here as needed.
            //Console.WriteLine("RegistrationIntentService - GCM Token: " + token);

            // save the value to send it to our server to manage the notifications
            App.UserPreferences.SetString("DeviceToken", token);
        }

        void Subscribe(string token)
        {
            var pubSub = GcmPubSub.GetInstance(this);
            // subscribe to a topic group
            pubSub.Subscribe(token, "/topics/global", null); // working

        }

    }
}

Then the InstanceIDListenerService file:

using Android.App;
using Android.Content;
using Android.Gms.Gcm.Iid;


namespace TruckMe.Droid.Infrastructure.Notifications
{
    [Service(Exported = false), IntentFilter(new[] { "com.google.android.gms.iid.InstanceID" })]
    public class MyInstanceIdListenerService : InstanceIDListenerService
    {
        /**
         * Called if InstanceID token is updated. This may occur if the security of the previous token had been compromised. 
         * This call is initiated by the InstanceID provider.
        */
        public override void OnTokenRefresh()
        {
            // Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
            var intent = new Intent(this, typeof(RegistrationIntentService));
            StartService(intent);
        }
    }
}

Let me know if the code to understand and analyze the problem is enough, thanks in advance

[Edit] I forgot to add also the 'MainActivity' file where the registration service is called:

using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Gms.Common;
using Android.Graphics.Drawables;
using Android.Views;
using Android.OS;
using Xamarin;
using TruckMe.Droid.Infrastructure;
using TruckMe.Droid.Infrastructure.Notifications;


namespace TruckMe.Droid
{
    [Activity(Label = "TruckMe",
          MainLauncher = false,
          ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
          WindowSoftInputMode = SoftInput.AdjustPan)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
    {

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            if (IsPlayServicesAvailable())
            {
                var intent = new Intent(this, typeof(RegistrationIntentService));
                StartService(intent);
            }

            global::Xamarin.Forms.Forms.Init(this, bundle);

            FormsMaps.Init(this, bundle);

            //Forms.SetTitleBarVisibility(AndroidTitleBarVisibility.Never);
            LoadApplication(new App());

            // below the code to remove the icon from android navigation bar
            ActionBar.SetIcon(new ColorDrawable(Android.Graphics.Color.Transparent)); 

            App.Init(new AndroidUserPreferences());
        }


        public bool IsPlayServicesAvailable()
        {
            int resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
            if (resultCode != ConnectionResult.Success)
            {
                if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode))
                    Console.WriteLine(GoogleApiAvailability.Instance.GetErrorString(resultCode));
                else
                {
                    Console.WriteLine("Sorry, this device is not supported");
                    Finish();
                }
                return false;
            }
            else
            {
                Console.WriteLine("Google Play Services is available.");
                return true;
            }
        }

    }
}

Upvotes: 2

Views: 1641

Answers (2)

Alberto
Alberto

Reputation: 61

I realized that it was not a metter of time, to make the token expired, but the fact that when you build the solution and transfer the app, it makes a sort of uninstall (keeping the old data) but if the app is already installed the GCM doesn't work with the 'old' token. I cleaned the data on the device and downloaded the app in the real device, after did the same in the emulator and sent the notifications, both were working. Then a just re-build the solution and downloaded only in the emulator; sent again the notifications but only the real device received it.

If you're debugging and you don't want to delete the app or data every time just add the code below where you call the registration Service.

    var instanceID = InstanceID.GetInstance(this);

        // See https://developers.google.com/cloud-messaging/android/start for details on this file.
        var token = instanceID.GetToken(SENDER_ID, GoogleCloudMessaging.InstanceIdScope, null);

#if DEBUG
        instanceID.DeleteToken(token, GoogleCloudMessaging.InstanceIdScope);
        instanceID.DeleteInstanceID();
#endif

        token = instanceID.GetToken(SENDER_ID, GoogleCloudMessaging.InstanceIdScope, null);

That part of code was suggested to me from the below link: fromXamarinForum

Upvotes: 2

Mr.Rebot
Mr.Rebot

Reputation: 6791

Have you tried to check this out:

Obtain a registration token

An Android application needs to register with GCM connection servers before it can receive messages. When an app registers, it receives a registration token and sends it to the app server. The client app should store a boolean value indicating whether the registration token has been sent to the server.

To use this API, include InstanceIDListenerService in the manifest:

<service android:name="[.MyInstanceIDService]" android:exported="false">
  <intent-filter>
         <action android:name="com.google.android.gms.iid.InstanceID"/>
  </intent-filter>
</service>

here is the link for more information about obtaining token.

Upvotes: 0

Related Questions