I am seeing this issue on my push notifications server - different Android devices (identified by their IMEI) receive the SAME registration id from Google's GCM service.
Isn't the registration id supposed to be unique? at east for the same application or GCM API Key?
I saw this thread but there doesn't seem to be an answer there:
Can two different devices have same GCM Registration ID?
Any help would be much appreciated
EDIT
here is the relevant code for registration:
Intent intent = new Intent(
"com.google.android.c2dm.intent.REGISTER");
intent.setPackage("com.google.android.gsf");
intent.putExtra("app",
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
intent.putExtra("sender", flatSenderIds);
context.startService(intent);
The only idea that comes to my mind is that this deprecated method will assign the same ID on different devices if the extra value for sender is the same on all of them. That differs from the current version where you don't say who you are, you don't even send an Intent, you just call register() and Google determines who you are and what ID you should be given.
If you finally use this new library, there's an example snippet of how to register (inside an AsyncTask or a Thread):
GoogleCloudMessaging gcm = null;
try {
// Will be for the first time
if (gcm == null)
gcm = GoogleCloudMessaging.getInstance(your_context);
// Registration against the GCM service
String regid = gcm.register(YOUR_SENDER_ID_OBTAINED_FROM_YOUR_PROJECT);
// You'll need to send the registration info to your remote server
registerRemoteServer(regid);
Log.d("MyGCM", "Registered on GCM as " + regid);
}
catch (final IOException ex) { ex.printStackTrace(); }
Sometimes Google changes the registration ID and you'll have multiple IDs associated. The server that sends the notification (your server) has to update the database with the new ID.
For more info check this document:
http://developer.android.com/google/gcm/adv.html
they says:
It's an Canonical IDs
On the server side, as long as the application is behaving well,
everything should work normally. However, if a bug in the application
triggers multiple registrations for the same device, it can be hard to
reconcile state and you might end up with duplicate messages.
GCM provides a facility called "canonical registration IDs" to easily
recover from these situations. A canonical registration ID is defined
to be the ID of the last registration requested by your application.
This is the ID that the server should use when sending messages to the
device.
If later on you try to send a message using a different registration
ID, GCM will process the request as usual, but it will include the
canonical registration ID in the registration_id field of the
response. Make sure to replace the registration ID stored in your
server with this canonical ID, as eventually the ID you're using will
stop working.
Well, after adding your registration code, I can see you are using the old method of registration. That method has been depecated since the middle of last year. It is likely to be less reliable than the new registration method.
You should try registering via the GoogleCloudMessaging.register method of the Google Play Services library. That's the recommended way by Google. See the official demo here.
Related
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.
I have a list of GCM registered users and their corresponding registration Ids in a database table, and I actually want to unregister a user whenever he/she is deleted from the table. I found a lot of examples here in Stackoverflow, but most of them are based on the old GCMRegistrar API which is now deprecated. I'm using GoogleCloudMessaging API and registering a user by following method:
private void registerUser(){
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(getBaseContext());
String regId = "";
try {
regId = gcm.register(getString(R.string.project_number));
Log.i("registrationId", regId);
}
catch (IOException e) {
Log.i("Registration Error", e.getMessage());
}
}
I have an administrator app, which acts as a 3rd-party application server, since it pushes notifications to all users. I want to unregister a specific user from this administrator app with following method:
private void unRegister(String regId) {
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(getBaseContext());
try {
gcm.unregister();
}
catch (IOException e) {
System.out.println("Error Message: " + e.getMessage());
}
}
But it confuses me that unregister() method does not take registration Id as an argument, which makes it impossible to unregister a specific device. Is there any way to unregister a specific device from GCM by registration Id ?.
This method does the trick:
gcm.unregister();
However, gcm.unregister() is now deprecated, hence, you must use one of these:
InstanceID.deleteToken() or InstanceID.deleteInstanceID().
These methods take the following parameters:
public void deleteToken (String authorizedEntity, String scope)
Being authorized entity the entity that you want to remove...
// THE FOLLOWING EXPLANATION IS ONLY FOR "UNREGISTER"
So, based on your comment:
But it confuses me that unregister() method does not take registration
Id as an argument, which makes it impossible to unregister a specific
device.
That's because you are expecting it to work in a way that it doesn't. It seems like you want to be able to make an http request passing a parameter to uregister(e.g. "http://www.gcmserver.com?unregisterid=xxxx"), and that's not the way it works, this is the way it works based on Google's Documentation:
How unregistration works
An application can be automatically unregistered after it is
uninstalled from the device. However, this process does not happens
right away, as Android does not provide an uninstall callback. What
happens in this scenario is as follows:
The end user uninstalls the application.
The 3rd-party server sends a message to GCM server.
The GCM server sends the message to the device.
The GCM client receives the message and queries Package Manager about
whether there are broadcast receivers configured to receive it, which
returns false.
The GCM client informs the GCM server that the
application was uninstalled.
The GCM server marks the registration ID for deletion.
The 3rd-party server sends a message to GCM.
The GCM returns a NotRegistered error message to the 3rd-party server.
The 3rd-party deletes the registration ID.
So, based on that, what the method gcm.unregister() actually does is marking that device for deletion(think of it as forcing the first steps of the process without actually uninstalling the app), letting the server know that it no longer needs to get notifications, also by not taking an "Id" as a parameter it means that it is referencing to that specific device.
Regards!
unregister() is now deprecated:
https://developers.google.com/android/reference/com/google/android/gms/gcm/GoogleCloudMessaging.html#unregister()
Quoting the docs you should call:
Instead use InstanceID.deleteToken() or InstanceID.deleteInstanceID().
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.