I've been reading on canonical IDs in GCM and how they help to rectify sending duplicate push notifications and with security. But now with Firebase Cloud Messaging (FCM), does this issue still exist?
I am the registration part has been taken away from the developer now and we just wait for a token refresh as per below:
public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
#Override
public void onTokenRefresh() {
// Get updated registration ID
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Logger.d("Refreshed FCM token: " + refreshedToken);
}
}
Some info on canonical IDs can be found here.
Update:
I recently revisited this topic on Canonical IDs and have concluded the following.
In FCM, it seems the Canonical IDs are no longer used (or at the very least extremely rarely) because of the way the Instance ID service works. To put it simply, the service works that there would only be one valid token per App Instance.
If the older token expires (for whichever reason), FCM triggers a tokenRefresh event where you would get a new registration token, and where you must also handle it accordingly (in onTokenRefresh()).
Short answer, Yes. It's still necessary.
The onTokenRefresh() method is expected to trigger whenever the token is actually refreshed. From there, it's the developer's responsibility to send the registration token towards an App Server.
BUT in an event where you weren't able to get a hold of the new registration token (e.g. forgot to save it, deleted it and only have the previous registration token, etc.), it may result to you (the developer) to send towards a supposed to be no longer valid registration token. That's when Canonical IDs come in.
I guess you can treat Canonical IDs as another safety measure so that developers can still get a hold of the valid registration token. Details about Canonical IDs (how it is handled and stuff) are mentioned in the FCM docs here.
Related
I have two types of questions around FirebaseInstanceId.getToken(String authorizedEntity, String scope), one around calling this method multiple times and one around whether calling this method triggers FirebaseMessagingService.onNewToken(String token).
1) Calling multiple times:
According to this documentation one would call getToken(String authorizedEntity, String scope) multiple times, each time with a different sender id, in order to be able to receive messages from multiple senders. My question is, will each call return a different token, or will each call return the same token but now the token will work also for multiple senders? If we call this method with a sender id that we've previously used before, will that return the existing token or generate a new one?
So, say I have this order of operation
Call getToken("senderId1", "FCM") and get token A
Call getToken("senderId2", "FCM"). Will I get A or a different token B?
Call getToken("senderId2", "FCM"). Will I get A, B, or yet another different one C?
2) Will onNewToken be called?
This documentation states that the method will be invoked if the token changes. So does this mean that if getToken returns a different token than before then onNewToken will be invoked as well? If we're going to be calling getToken multiple times to allow for receiving from different senders, and each call returns a different token, then onNewToken will keep getting invoked.
Since it is advised that we update our server when onNewToken is triggered, I want to understand the expected behavior and avoid generally updating the server on each invocation of onNewToken.
My question is, will each call return a different token, or will each call return the same token but now the token will work also for multiple senders?
getToken() / getToken(String, String) will return the same token until such time that the corresponding token expires. Note that by same token, I mean the same token that they return for each sender. i.e.:
getToken() returns the registration token for the default project (e.g. tokenDefaultSenderId)
getToken(String, String) returns the registration token for the sender it is associated to (e.g. tokenSenderId2)
If we call this method with a sender id that we've previously used before, will that return the existing token or generate a new one?
Okay.
You will get token B.
You will get token B again.
A token is tied to the sender it is associated to.
Will onNewToken be called? ... So does this mean that if getToken returns a different token than before then onNewToken will be invoked as well?
onNewToken() will only return the token for the default sender (emphasis mine):
Called when a new token for the default Firebase project is generated.
The thing about onNewToken() is that it triggers only when the previous token has expired -- the thing to ask is, if the token for the default sender expires, what more for the other senders? So the best workaround here is to call getToken() for each of the sender that you have, like so:
public void onNewToken(String token){
String default = token;
String sender2 = getToken("senderId2", "FCM");
// and so on for each of your sender, then save the tokens as needed
}
The token is unique for every sender id. Different sender ids have different tokens.
Default sender is the one defined in your google-services.json (related to the firebase project that the app is connected to)
OnNewToken is called when token for the default sender is changed. There are no callbacks triggered when a token for a specific sender (other than default) is changed.
As mentioned by #sNash who contacted firebase support:
I contacted firebase support team and got an answer. Answer summary : Different sender id's token will not be automatically managed by firebase cloud messaging. So developer is responsible for it's management. You guys have to check it's validity manually with your own method.
How to determine if token needs to be refreshed in case of multiple sender id?
Managing tokens for specific sender ids (other than default):
One simple solution for managing tokens for specific sender ids (other than default) is through storing all sender ids with their tokens in SharedPreferences or in db. When app starts, check if the token changed for each sender by comparing the stored token with the token returned by
FirebaseInstanceId.getInstance().getToken(SENDER_ID, "FCM");
Moreover, do the same check in onNewToken method. There is a chance that tokens other than the default may be changed when the default token is changed.
I have a question about the correct use of GCM-IDs.
At the moment I have a InstanceIDListenerService a GcmListenerService and a RegestrationIntentService.
The RegeistrationIntentServie get started in the MainActivity every time someone opens the app.
I think this is a correct implementation of the Google guidelines.
But what is the best way to handle the GCM-ID so that I will not have incorrect GCM-IDs on my server after the refresh in the InstandeIDListenerService. Because at the moment the refresh only registers a new GCM-ID on my server.
Would it be an idea to generate a random Device ID so that I can update the old GCM-ID?
How do you handle the IDs?
Because at the moment I ask the server every start of the app if he knows a GCM-ID in combination with a (randomly generated) Device-ID and update one of both if the other one is incorrect or does not exist.
And I think that produces a lot of network traffic for nothing.
Just to be sure, you are not calling InstanceIDListenerService.onTokenRefresh() yourself right? This will be called by the system if necessary.
To answer your question, you should use the GCM functionality called "canonical IDs":
Canonical IDs
If a bug in the client app triggers multiple registrations for the
same device, it can be hard to reconcile state and the client app
might end up with duplicate messages.
Implementing canonical IDs can help you more easily recover from these
situations. A canonical registration ID is the registration token of
the last registration requested by the client app. This is the ID
that the server should use when sending messages to the device.
If you try to send a message using an old registration token, GCM will
process the request as usual, but it will include the canonical ID in
the registration_id field of the response. Make sure to replace the
registration token stored in your server with this canonical ID, as
eventually the old registration token will stop working.
To be more precise, if there are two registrations for the same device and you send a notification using the older registration ID, you will get the canonical ID (the registration ID of the newest registration for this device). If this ID is already stored on your server, delete the old registration. If the canonical ID is not stored on your server for any reason, replace the registration ID you used to send the notification with the canonical ID.
#leet GCM ID token initiates callback periodically when your token needs to be refresh, or sometime when:
- Security issues; like ssl or platform issues
- Device info is no longer valid; for example backup and restore.
- Instance ID service is otherwise affected.
public class MyInstanceIDService extends InstanceIDListenerService {
public void onTokenRefresh() {
refreshAllTokens();
}
private void refreshAllTokens() {
// assuming you have defined TokenList as
// some generalized store for your tokens
ArrayList<TokenList> tokenList = TokensList.get();
InstanceID iid = InstanceID.getInstance(this);
for(tokenItem : tokenList) {
tokenItem.token =
iid.getToken(tokenItem.authorizedEntity,tokenItem.scope,tokenItem.options);
// send this tokenItem.token to your server
}
}
};
Keeping the Registration State in
Sync
To protect the client app and app server from potential malicious
re-use of registration tokens, you should periodically initiate token
refresh from the server. When GCM registration token refresh is
initiated from server side, the client app must handle
a tokenRefreshed message with the GCM registration client/server
handshake. This link may help you verify on how
to handle Refresh Token.
As Baris have metioned, your server must use canonical IDs when you are sending message to the device. The tokenRefresh would remove the idea of generating a random device ID and Canonical IDs would solve on how you verify if the ID is correct or not.
Is there something wrong in the fact that every time I log in to my android application, the first thing I do is to register to GCM.
I know it's not necessary to do this, but in order to check if the registration id is refreshed to a new one or it s still the same, I plan to re-register.
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
GCMRegistrar.register(this, SENDER_ID);
} else {
Log.v(TAG, "Already registered");
}
I would also like to know if by re-registering to GCM I am somehow forcing the registrationID to expire.
Thank you!!!
Part 1:
In order to check if the registration id is refreshed to a new one or it is still the same, I plan to re-register.
Now, if you see the second point under the heading Enable GCM on Architectural Overview page, it says:
Note that Google may periodically refresh the registration ID, so you should design your Android application with the understanding that the com.google.android.c2dm.intent.REGISTRATION intent may be called multiple times. Your Android application needs to be able to respond accordingly.
So, Google automatically sends you this broadcast, when it renews the ID. You might not necessarily send check the emptyness of the ID. When Google changes the ID, then also it won't be empty, right? So if you want to handle the renewal/refreshing of the ID then you may do the following:
You should have a Broadcast Listener which could handle com.google.android.c2dm.intent.REGISTRATION intent, which Google send to the app when it has to refresh the registration ID. The broadcast receiver will have the onReceive method with an Intent. From the intent you can get the Bundle using which you can extract the new registration ID from Google. You can save that and send it to the 3rd part server to replace your previous registered ID for that user.
Also you may see this answer on the question In GoogleCloudMessaging API, how to handle the renewal or expiration of registration ID?.
Part 2:
Answering your second part of question:
You may want to read Unregistration here. The docs says If you unregister and then re-register, GCM may return the same ID or a different ID—there's no guarantee either way.
I think that whenever an ID will be about to expire/renew, Google will send you the new ID. Then the response from Google will contain a Canonical Registration ID (which is the new registration ID). This response indicates that your server should delete the old registration ID and use only the new one. ( Source : Answer at Unregistering and re-registering for GCM messages causes two regId's to be valid. Is this as intended? question by #Eran)
Hope this helps you understand how to handle it.
In the docs on Google Cloud Messaging, it states:
The Android application should store this ID for later use (for
instance, to check on onCreate() if it is already registered). Note
that Google may periodically refresh the registration ID, so you
should design your Android application with the understanding that the
com.google.android.c2dm.intent.REGISTRATION intent may be called
multiple times. Your Android application needs to be able to respond
accordingly.
I register my device using the following code:
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
String regID = gcm.register(senderID);
The GoogleCloudMessaging class encapsulates the registration process. So how am I suppose to handle com.google.android.c2dm.intent.REGISTRATION since handling that is done internally by the GoogleCloudMessaging class?
That's an interesting question.
Google encourage you to switch to the new registration process :
An Android application running on a mobile device registers to receive messages by calling the GoogleCloudMessaging method register(senderID...). This method registers the application for GCM and returns the registration ID. This streamlined approach replaces the previous GCM registration process.
The note that says Google may periodically refresh the registration ID only appears on the page that still shows the old registration process, so it's possible that this note is no longer relevant.
If you want to be safe, you can still use the old registration process. Or you can use the new process, but have in addition the code that handles the com.google.android.c2dm.intent.REGISTRATION intent, in order to make sure you are covered if Google do decide to refresh the registration ID.
That said, I never experienced such a refresh, and even when I did experience a change in the registration ID (usually as a result of sending a notification after un-installing the app and then re-installing it), the old registration ID still worked (resulting in a canonical registration ID sent in the response from Google), so no harm was done.
EDIT (06.06.2013) :
Google changed their Demo App to use the new interface. They refresh the registration ID by setting an expiration date on the value persisted locally by the app. When the app starts, they load their locally stored registration id. If it is "expired" (which in the demo means it was received from GCM over 7 days ago), they call gcm.register(senderID) again.
This doesn't handle the hypothetical scenario in which a registration ID is refreshed by Google for an app that hasn't been launched for a long time. In that case, the app won't be aware of the change, and neither will the 3rd party server.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mDisplay = (TextView) findViewById(R.id.display);
context = getApplicationContext();
regid = getRegistrationId(context);
if (regid.length() == 0) {
registerBackground();
}
gcm = GoogleCloudMessaging.getInstance(this);
}
/**
* Gets the current registration id for application on GCM service.
* <p>
* If result is empty, the registration has failed.
*
* #return registration id, or empty string if the registration is not
* complete.
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.length() == 0) {
Log.v(TAG, "Registration not found.");
return "";
}
// check if app was updated; if so, it must clear registration id to
// avoid a race condition if GCM sends a message
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion || isRegistrationExpired()) {
Log.v(TAG, "App version changed or registration expired.");
return "";
}
return registrationId;
}
/**
* Checks if the registration has expired.
*
* <p>To avoid the scenario where the device sends the registration to the
* server but the server loses it, the app developer may choose to re-register
* after REGISTRATION_EXPIRY_TIME_MS.
*
* #return true if the registration has expired.
*/
private boolean isRegistrationExpired() {
final SharedPreferences prefs = getGCMPreferences(context);
// checks if the information is not stale
long expirationTime =
prefs.getLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, -1);
return System.currentTimeMillis() > expirationTime;
}
EDIT (08.14.2013) :
Google changed their Demo App again (two days ago). This time they removed the logic that considers the Registration ID to be expired after 7 days. Now they only refresh the Registration ID when a new version of the app it installed.
EDIT (04.24.2014) :
For the sake of completeness, here are the words of Costin Manolache (taken from here), a Google developer involved in the development of GCM, on the matter :
The 'periodical' refresh never happened, and the registration refresh
is not included in the new GCM library.
The only known cause for registration ID change is the old bug of apps
getting unregistered automatically if they receive a message while
getting upgraded. Until this bug is fixed apps still need to call
register() after upgrade, and so far the registration ID may change in
this case. Calling unregister() explicitly usually changes the
registration ID too.
The suggestion/workaround is to generate your own random identifier,
saved as a shared preference for example. On each app upgrade you can
upload the identifier and the potentially new registration ID. This
may also help tracking and debugging the upgrade and registration
changes on server side.
This explains the current implementation of the official GCM Demo application.
com.google.android.c2dm.intent.REGISTRATION should never be handled when using the GoogleCloudMessaging class to register.
Reading the new InstanceID API, I found more info on when the token might change:
Your app can request tokens from the Instance ID service as needed
using the getToken() method, and like InstanceID, your app can also
store tokens on your own server. All tokens issued to your app belong
to the app's InstanceID.
Tokens are unique and secure, but your app or the Instance ID service
may need to refresh tokens in the event of a security issue or when a
user uninstalls and reinstalls your app during device restoration.
Your app must implement a listener to respond to token refresh
requests from the Instance ID service.
More details:
The Instance ID service initiates callbacks periodically (for example,
every 6 months), requesting that your app refreshes its tokens. It may
also initiate callbacks when:
There are security issues; for example, SSL or platform issues.
Device information is no longer valid; for example, backup and restore.
The Instance ID service is otherwise affected.
Sources:
https://developers.google.com/instance-id/
https://developers.google.com/instance-id/guides/android-implementation
After scrubbing through tonnes of misleading answers across the net, including SO, the only place I found a complete answer was as remarked by Eran's answer and here:
While automatic registration refresh might or might never have happened, google describes a simiple algorithm to handle the canocical_ids by parsing successful response:
If the value of failure and canonical_ids is 0, it's not necessary to parse the remainder of the response. Otherwise, we recommend that you iterate through the results field and do the following for each object in that list:
If message_id is set, check for registration_id:
If registration_id is set, replace the original ID with the new value (canonical ID) in your server database. Note that the original ID is not part of the result, so you need to obtain it from the list of code>registration_ids passed in the request (using the same index).
Otherwise, get the value of error:
If it is Unavailable, you could retry to send it in another request.
If it is NotRegistered, you should remove the registration ID from your server database because the application was uninstalled from the device or it does not have a broadcast receiver configured to receive com.google.android.c2dm.intent.RECEIVE intents.
Otherwise, there is something wrong in the registration ID passed in the request; it is probably a non-recoverable error that will also require removing the registration from the server database. See Interpreting an error response for all possible error values.
From aforementioned link.
I am trying to migrate to GCM in Android, C2DM now being deprecated. The registration process described here is different from registration described here. Are both registration same? Can we see code for GCMRegistrar to know for sure?
I've successfully migrated my C2DM project to GCM. Tested, it works fine. The only changes were:
in the Android app - change the value of sender upon registration
on the server side - change the auth header and the URL
That was it, as far as the interaction with Google goes. There were more some changes dictated by the app's logic:
in the Android app, the registration ID was cached in its preferences. Upon upgrade, I remove reg ID from the preferences to force re-registration, this time with GCM.
the logic of passing the reg ID to the server got an extra boolean parameter - if this is a C2DM or GCM reg ID
the logic of sending messages became conditional upon the said parameter.
Throwing out the C2DM logic completely out of the server would be unwise - not everyone upgrades their Android apps. The old, C2DM-enabled versions will be out in the wild for some time. And Google pledged to keep C2DM running in the short term. So message sending is conditional - depending on reg ID type, it sends either to GCM or to C2DM.
EDIT re: conditional logic:
if($RegID_Is_GCM)
{
$Auth = GCM_Auth();
$URL = $GCM_URL;
}
else
{
$Auth = C2DM_AUTH();
$URL = $C2DM_URL;
}
They are actually the same thing. The second one encapsulates the first one in a static method and registers a broadcast receiver. You can attach the source to the gcm.jar and see for yourself. You can find source code in ~/android-sdks/extras/google/gcm/gcm-client/gcm-src.jar
The Thing I like most in GCM is the RegID we will get from GCM server,it is not only an ID its an Address of this application on this Device. So this time you don't need to send a device Id to server along with your Registration Id as per was in C2DM.
In C2DM every time you request a registration id you will get a new ID.
But in GCM RegId generated by using your application package along with some device id so if you will request for Registration Id again and again you will receive the same RegId.
And if you uninstall an application and will install it again still GCM server will give you the same Registration Id.
So one Registraion Id will do no need of any Device Id to send to server.
I have been successful at migrating from C2DM to GCM. I have also documented how to implement GCM at
http://android.amolgupta.in/2012/07/google-cloud-messaging-gcm-tutorial.html
GCMRegistrar is just a helper that does the leg work described in the first page.
You can see the class here. android-sdk\extras\google\gcm\gcmclient\src\com\google\android\gcm.