I've implemented FCM on my server with an Android client, and it worked fine for a while. All of a sudden, the client stopped receiving notifications for 5-10 minutes at a time, after which it would get all the pending notifications at once. The logs show that the server is sending the messages correctly.
When the message queue gets stuck, not even test messages from the Firebase console get through.
The only relevant thing I've found in the official documentation was regarding the lifetime of a message:
If the device is not connected to FCM, the message is stored until a connection is established (again respecting the collapse key rules). When a connection is established, FCM delivers all pending messages to the device.
But my network is working fine, and this issue has arisen on other networks and clients as well. When I freshly install the app, a new token is being generated and successfully received by my server. It's just the server-to-client messages that aren't going through.
How can I determine if the connection is established and fix it? Or what else could be the cause of this?
This is my client-side code
AndroidManifest.xml
<application
android:name=".App"
android:allowBackup="false"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:largeHeap="true"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<service
android:name=".service.NotificationService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
NotificationService.java
public class NotificationService extends FirebaseMessagingService {
private static final String TAG = "NotificationService";
private static String token;
public NotificationService() {
token = PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.getString("firebase-token", null);
}
#Override
public void onNewToken(#NonNull String s) {
super.onNewToken(s);
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
#Override
public void onComplete(#NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}
token = task.getResult().getToken();
Log.d(TAG, "onComplete: token = " + token);
PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.edit()
.putString("firebase-token", token)
.apply();
}
});
}
#Override
public void onMessageReceived(final RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d(TAG, "Received message of size " + remoteMessage.getData().size());
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
Handler h = new Handler(Looper.getMainLooper());
h.post(new Runnable() {
public void run() {
int left = -1, right = -1;
if (remoteMessage.getData().get("left") != null) {
left = Integer.parseInt(Objects.requireNonNull(remoteMessage.getData()
.get("left")));
}
if (remoteMessage.getData().get("right") != null) {
right = Integer.parseInt(Objects.requireNonNull(remoteMessage.getData()
.get("right")));
}
if (remoteMessage.getData().get("REFRESH") != null) {
Util.sendTitleBroadcast(getApplicationContext(),
left, right,
Util.REFRESH_TITLE);
} else {
Util.sendTitleBroadcast(getApplicationContext(),
left, right,
Util.TITLE_DATA_RECEIVED);
}
}
});
}
}
public static String getToken() {
if (token == null) {
token = PreferenceManager
.getDefaultSharedPreferences(App.getContext())
.getString("firebase-token", null);
}
return token;
}
}
Code for server-side
FirebaseMessagingService.java
public class FirebaseMessagingService {
private static final Logger logger = Logger
.getLogger(FirebaseMessagingService.class);
private static FirebaseMessagingService instance;
/**
* Path to resource file that contains the credentials for Admin SDK.
*/
private final String keyPath = "firebase-adminsdk.json";
/**
* Maps a token list to each topic Object.
*/
private static Map<Object, List<String>> topicTokenMap;
public synchronized static FirebaseMessagingService getInstance() {
if (instance == null) {
instance = new FirebaseMessagingService();
}
return instance;
}
private FirebaseMessagingService() {
init();
}
/**
* Initializes the Firebase service account using the credentials from the
* json file found at keyPath.
*/
private void init() {
try {
InputStream serviceAccount
= getClass().getClassLoader().getResourceAsStream(keyPath);
if (serviceAccount != null) {
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials
.fromStream(serviceAccount))
.setDatabaseUrl(databaseUrl)
.build();
FirebaseApp.initializeApp(options);
logger.debug("FirebaseMessagingService: init successful");
} else {
logger.debug("FirebaseMessagingService: Input stream null from"
+ " path: " + keyPath);
}
} catch (IOException ex) {
logger.debug("FirebaseMessagingService: Failed to get credentials "
+ "from inputStream." + ex.getMessage());
}
}
/**
* Sends the messages given as parameters to the list of tokens mapped to
* the given topic Object.
*
* #param topic
* #param messages
*/
public void sendMessage(Object topic,
Map<String, String> messages) {
logger.debug("FirebaseMessagingService: sending Message "
+ messages.toString());
try {
if (topicTokenMap != null) {
List<String> tokenList = topicTokenMap.get(topic);
if (tokenList != null) {
tokenList.removeAll(Collections.singleton(null));
MulticastMessage message = MulticastMessage.builder()
.putAllData(messages)
.addAllTokens(tokenList)
.build();
FirebaseMessaging.getInstance().sendMulticast(message);
logger.debug("FirebaseMessagingService: message sent successfully");
}
}
} catch (FirebaseMessagingException ex) {
logger.debug(ex.getMessage());
}
}
/**
* Registers the token given as a parameter to the topic Object.
*
* #param topic
* #param token
*/
public void registerTokenToTopic(Object topic, String token) {
if (topicTokenMap == null) {
topicTokenMap = new HashMap<Object, List<String>>();
}
List<String> tokens = topicTokenMap.get(topic);
if (tokens == null) {
tokens = new ArrayList<String>();
}
if (!tokens.contains(token)) {
tokens.add(token);
}
topicTokenMap.put(topic, tokens);
}
/**
* Unregisters all instances of the token given as a parameter from the topic
* given.
*
* #param topic
* #param token
*/
public void unregisterTokenFromTopic(Object topic, String token) {
if (topicTokenMap != null) {
List<String> tokens = topicTokenMap.get(topic);
if (tokens != null) {
tokens.removeAll(Collections.singleton(token));
}
}
}
/**
* Looks for a topic that has a field with the name equal to topicFieldName
* and the value equal to topicFieldValue and sends a notification to the
* tokens subscribed to that topic, telling them to ask for an update.
*
* #param topicFieldName
* #param topicFieldValue
*/
public void notifyChangeForTopicField(String topicFieldName,
Object topicFieldValue) {
logger.debug("FirebaseMessagingService: notifyChangeForTopicField: "
+ "topicFieldValue = " + topicFieldValue.toString());
Map<String, String> messageMap = new HashMap<String, String>();
messageMap.put("REFRESH", "true");
if (topicTokenMap != null
&& topicTokenMap.entrySet() != null
&& topicTokenMap.entrySet().size() > 0) {
Iterator it = topicTokenMap.entrySet().iterator();
while (it.hasNext()) {
try {
Map.Entry pair = (Map.Entry) it.next();
Object topic = pair.getKey();
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField topic = " + topic.toString());
Field field = topic.getClass().getDeclaredField(topicFieldName);
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField field = " + field.toString());
field.setAccessible(true);
logger.debug("FirebaseMessagingService: "
+ "notifyChangeForTopicField field contains topic: "
+ (field.get(topic) != null ? "true" : "false"));
if (field.get(topic) != null
&& field.get(topic).equals(topicFieldValue)) {
sendMessage(topic, messageMap);
break;
}
it.remove();
} catch (NoSuchFieldException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (SecurityException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalArgumentException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(FirebaseMessagingService.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
EDIT: after a few more tests I've found that when one client stops working, the others receive the messages just fine
Your onMessageReceived() looks a bit overkill to me. I do not think you have to create your own Handler to process data. Just stick to the Firebase example: FirebaseExample
override fun onMessageReceived(remoteMessage: RemoteMessage) {
if (remoteMessage.data.isNotEmpty()) {
Log.d(TAG, "Message data payload: ${remoteMessage.data}")
if (/* Check if data needs to be processed by long running job */ true) {
// For long-running tasks (10 seconds or more) use WorkManager.
scheduleJob()
} else {
// Handle message within 10 seconds
handleNow()
}
}
}
I've found the answer here: https://stackoverflow.com/a/23902751/10702209
What happened was that I was getting router timeouts after 5 minutes, randomly. I've fixed the issue by manually sending out heartbeats to FCM after 4 minutes, instead of the default 15 minutes.
context.sendBroadcast(new Intent("com.google.android.intent.action.GTALK_HEARTBEAT"));
context.sendBroadcast(new Intent("com.google.android.intent.action.MCS_HEARTBEAT"));
I've implemented this fix a couple of months ago and haven't had the issue come up again since.
Related
I have an application that is now being developed for IOS. I need them pushed between the platforms. As it is written today, in the backend, it works from Android for Android, but I changed the registration of the devices, so that they were registered with the respective templates (of each platform).
More, it stopped working obviously because it is written from Android to Android.
The record in the hub is correct. What should I change for push to be sent?
Backend code:
public static async void enviarPushNotification(ApiController controller, DataObjects.Notification notification)
{
// Get the settings for the server project.
HttpConfiguration config = controller.Configuration;
MobileAppSettingsDictionary settings =
controller.Configuration.GetMobileAppSettingsProvider().GetMobileAppSettings();
// Get the Notification Hubs credentials for the Mobile App.
string notificationHubName = settings.NotificationHubName;
string notificationHubConnection = settings
.Connections[MobileAppSettingsKeys.NotificationHubConnectionString].ConnectionString;
// Create a new Notification Hub client.
NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString(notificationHubConnection, notificationHubName);
// Android payload
JObject data = new JObject();
data.Add("Id", notification.Id);
data.Add("Descricao", notification.Descricao);
data.Add("Tipo", notification.Tipo);
data.Add("Id_usuario", notification.Id_usuario);
//data.Add("Informacao", notification.Informacao);
data.Add("Informacao", notification.Informacao);
data.Add("Status", notification.Status);
//alteração com a colocação da tag priority em caso de erro teste sem \"priority\": \"high\"
var androidNotificationPayload = "{ \"priority\": \"high\", \"data\" : {\"message\":" + JsonConvert.SerializeObject(data) + "}}";
// var androidNotificationPayload = "{ \"data\" : {\"message\":" + JsonConvert.SerializeObject(data) + "}}";
try
{
// Send the push notification and log the results.
String tag = "_UserId:" + notification.Id_usuario;
//var result = await hub.SendGcmNativeNotificationAsync(androidNotificationPayload);
var result = await hub.SendGcmNativeNotificationAsync(androidNotificationPayload, tag);
// Write the success result to the logs.
config.Services.GetTraceWriter().Info(result.State.ToString());
}
catch (System.Exception ex)
{
// Write the failure result to the logs.
config.Services.GetTraceWriter().Error(ex.Message, null, "Push.SendAsync Error");
}
}
Device code:
public class RegistrationIntentService extends IntentService {
private static final String TAG = "RegIntentService";
private NotificationHub hub;
public RegistrationIntentService() {
super(TAG);
}
public ApplicationUtils getApplicationUtils() {
return (ApplicationUtils) getApplication();
}
#Override
protected void onHandleIntent(Intent intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
try {
if (FirebaseInstanceId.getInstance() == null) {
FirebaseApp.initializeApp(this);
}
String FCM_token = FirebaseInstanceId.getInstance().getToken();
SaveSharedPreferences.setFCM(getApplicationContext(), FCM_token);
String registrationID = sharedPreferences.getString("registrationID", null);
if (registrationID == null) {
registerDevice(sharedPreferences, FCM_token);
} else {
String fcMtoken = sharedPreferences.getString("FCMtoken", "");
if (!fcMtoken.equals(FCM_token)) {
registerDevice(sharedPreferences, FCM_token);
}
}
} catch (Exception e) {
Log.e(TAG, "Failed to complete registration", e);
}
}
private void registerDevice(SharedPreferences sharedPreferences, String FCM_token) throws Exception {
String tag = "_UserId:" + getApplicationUtils().getUsuario().Id;
NotificationHub hub = new NotificationHub(NotificationSettings.HubName,
NotificationSettings.HubListenConnectionString, this);
String templateBodyGCM = "{\"data\":{\"message\":\"$(messageParam)\"}}";
TemplateRegistration registration = hub.registerTemplate(FCM_token, "simpleGCMTemplate", templateBodyGCM, tag);
String regID = registration.getRegistrationId();
sharedPreferences.edit().putString("registrationID", regID).apply();
sharedPreferences.edit().putString("FCMtoken", FCM_token).apply();
}
}
I have use alljoyn for wifi share. I want device list connected to wifi network on channel base.
I have follow one demo but it not call implemented method announced
AboutListener is part of alljoyn.
import org.alljoyn.bus.AboutListener;
public class OnboardingApplication extends Application implements AboutListener {
#Override
public void announced(String busName, int version, short port, AboutObjectDescription[] objectDescriptions, Map<String, Variant> aboutMap) {
Map<String, Object> newMap = new HashMap<String, Object>();
try {
newMap = TransportUtil.fromVariantMap(aboutMap);
String deviceId = (newMap.get(AboutKeys.ABOUT_APP_ID).toString());
String deviceFriendlyName = (String) newMap.get(AboutKeys.ABOUT_DEVICE_NAME);
m_logger.debug(TAG, "onAnnouncement received: with parameters: busName:" + busName + ", port:" + port + ", deviceid" + deviceId + ", deviceName:" + deviceFriendlyName);
addDevice(deviceId, busName, port, deviceFriendlyName, objectDescriptions, newMap);
} catch (BusException e) {
e.printStackTrace();
}
}
}
In order to get the announced method called you'll need to register your AboutListener:
org.alljoyn.bus.alljoyn.DaemonInit.PrepareDaemon(getApplicationContext());
//Bus Connection
Status status = mBus.connect();
//Check if connection is established
if (status != Status.OK) {
return;
}
//Setup Bus Attachment
mBus.useOSLogging(true);
mBus.setDebugLevel("ALLJOYN_JAVA", 7);
mBus.registerAboutListener(mListener);
//Start AboutData Listener
status = mBus.whoImplements(null);
if (status != Status.OK) {
Log.e(TAG, "whoImplements Error");
} else {
Log.w(TAG, "whoImplements Success");
}
mListener is your object that implements AboutListener.
When you call whoImplements(null) you are saying you want all announcements from all interfaces.
In addition to what LopesFigueiredo said, try creating your BusAttachment with a remote message policy of Receive. For example:
BusAttachment mBus = new BusAttachment("My Attachment", BusAttachment.RemoteMessage.Receive);
I am using Microsoft Cognitive Services - Speech Recognition on an Android application. Everything works fine when the code is in my main activity, but when I want to move the part corresponding to speech recognition to a new class, it throws an error.
Here are the code samples:
In the main activity
int m_waitSeconds = 0;
MicrophoneRecognitionClient micClient = null;
FinalResponseStatus isReceivedResponse = FinalResponseStatus.NotReceived;
Boolean speechIntent = false;
SpeechRecognitionMode speechMode = SpeechRecognitionMode.ShortPhrase;
public enum FinalResponseStatus { NotReceived, OK }
/**
* Gets the primary subscription key
*/
public String getPrimaryKey() {
return this.getString(R.string.primaryKey);
}
/**
* Gets the secondary subscription key
*/
public String getSecondaryKey() {
return this.getString(R.string.secondaryKey);
}
/**
* Gets the LUIS application identifier.
* #return The LUIS application identifier.
*/
private String getLuisAppId() {
return this.getString(R.string.luisAppID);
}
/**
* Gets the LUIS subscription identifier.
* #return The LUIS subscription identifier.
*/
private String getLuisSubscriptionID() {
return this.getString(R.string.luisSubscriptionID);
}
/**
* Gets the default locale.
* #return The default locale.
*/
private String getDefaultLocale() {
return "en-us";
}
/**
* Handles the Click event of the _startButton control.
*/
private void StartButton_Click(View v) {
this.m_waitSeconds = this.speechMode == SpeechRecognitionMode.ShortPhrase ? 20 : 200;
if (this.micClient == null) {
if (this.speechIntent) {
this.WriteLine("--- Start microphone dictation with Intent detection ----");
this.micClient = SpeechRecognitionServiceFactory.createMicrophoneClientWithIntent(
this,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getSecondaryKey(),
this.getLuisAppId(),
this.getLuisSubscriptionID());
}
else
{
this.micClient = SpeechRecognitionServiceFactory.createMicrophoneClient(
this,
this.speechMode,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getSecondaryKey());
}
}
this.micClient.startMicAndRecognition();
}
public void onFinalResponseReceived(final RecognitionResult response) {
boolean isFinalDicationMessage = this.speechMode == SpeechRecognitionMode.LongDictation &&
(response.RecognitionStatus == RecognitionStatus.EndOfDictation ||
response.RecognitionStatus == RecognitionStatus.DictationEndSilenceTimeout);
if (null != this.micClient && ((this.speechMode == SpeechRecognitionMode.ShortPhrase) || isFinalDicationMessage)) {
// we got the final result, so it we can end the mic reco. No need to do this
// for dataReco, since we already called endAudio() on it as soon as we were done
// sending all the data.
this.micClient.endMicAndRecognition();
}
if (isFinalDicationMessage) {
this.isReceivedResponse = FinalResponseStatus.OK;
}
Confidence cMax = Confidence.Low;
int iMax = 0;
if (!isFinalDicationMessage && response.Results.length != 0) {
for (int i = 0; i < response.Results.length; i++) {
//get the text with highest confidence:
if(response.Results[i].Confidence.getValue() > cMax.getValue()){
cMax = response.Results[i].Confidence;
iMax = i;
}
}
this.WriteLine(response.Results[iMax].DisplayText);
}
}
/**
* Called when a final response is received and its intent is parsed
*/
public void onIntentReceived(final String payload) {
this.WriteLine("--- Intent received by onIntentReceived() ---");
this.WriteLine(payload);
this.WriteLine();
}
public void onPartialResponseReceived(final String response) {
this.WriteLine("--- Partial result received by onPartialResponseReceived() ---");
this.WriteLine(response);
this.WriteLine();
}
public void onError(final int errorCode, final String response) {
this.WriteLine("--- Error received by onError() ---");
this.WriteLine("Error code: " + SpeechClientStatus.fromInt(errorCode) + " " + errorCode);
this.WriteLine("Error text: " + response);
this.WriteLine();
}
/**
* Called when the microphone status has changed.
* #param recording The current recording state
*/
public void onAudioEvent(boolean recording) {
if (!recording) {
this.micClient.endMicAndRecognition();
}
}
/**
* Writes the line.
*/
private void WriteLine() {
this.WriteLine("");
}
/**
* Writes the line.
* #param text The line to write.
*/
private void WriteLine(String text) {
System.out.println(text);
}
In a separate class
public class SpeechRecognition implements ISpeechRecognitionServerEvents
{
int m_waitSeconds = 0;
private MicrophoneRecognitionClient micClient = null;
private FinalResponseStatus isReceivedResponse =FinalResponseStatus.NotReceived;
private Boolean speechIntent = false;
private SpeechRecognitionMode speechMode=SpeechRecognitionMode.ShortPhrase;
public enum FinalResponseStatus { NotReceived, OK }
/**
* Gets the primary subscription key
*/
public String getPrimaryKey() {
return Integer.toString(R.string.primaryKey);
}
/**
* Gets the secondary subscription key
*/
public String getSecondaryKey() {
return Integer.toString(R.string.secondaryKey);
}
/**
* Gets the LUIS application identifier.
* #return The LUIS application identifier.
*/
private String getLuisAppId() {
return Integer.toString(R.string.luisAppID);
}
/**
* Gets the LUIS subscription identifier.
* #return The LUIS subscription identifier.
*/
private String getLuisSubscriptionID() {
return Integer.toString(R.string.luisSubscriptionID);
}
/**
* Gets the default locale.
* #return The default locale.
*/
private String getDefaultLocale() {
return "en-us";
}
public void startSpeechRecognition() {
this.m_waitSeconds = this.speechMode == SpeechRecognitionMode.ShortPhrase ? 20 : 200;
if (this.micClient == null) {
if (this.speechIntent) {
this.micClient = SpeechRecognitionServiceFactory.createMicrophoneClientWithIntent(
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getSecondaryKey(),
this.getLuisAppId(),
this.getLuisSubscriptionID());
}
else
{
this.micClient = SpeechRecognitionServiceFactory.createMicrophoneClient(
this.speechMode,
this.getDefaultLocale(),
this,
this.getPrimaryKey(),
this.getSecondaryKey());
}
}
this.micClient.startMicAndRecognition();
}
public void endSpeechRecognition(){
this.micClient.endMicAndRecognition();
}
public void onFinalResponseReceived(final RecognitionResult response) {
boolean isFinalDicationMessage = this.speechMode == SpeechRecognitionMode.LongDictation &&
(response.RecognitionStatus == RecognitionStatus.EndOfDictation ||
response.RecognitionStatus == RecognitionStatus.DictationEndSilenceTimeout);
if (null != this.micClient && ((this.speechMode == SpeechRecognitionMode.ShortPhrase) || isFinalDicationMessage)) {
// we got the final result, so it we can end the mic reco. No need to do this
// for dataReco, since we already called endAudio() on it as soon as we were done
// sending all the data.
this.micClient.endMicAndRecognition();
}
if (isFinalDicationMessage) {
this.isReceivedResponse = FinalResponseStatus.OK;
}
Confidence cMax = Confidence.Low;
int iMax = 0;
if (!isFinalDicationMessage && response.Results.length != 0) {
for (int i = 0; i < response.Results.length; i++) {
//get the text with highest confidence:
if(response.Results[i].Confidence.getValue() > cMax.getValue()){
cMax = response.Results[i].Confidence;
iMax = i;
}
}
System.out.println("Action to take: " + response.Results[iMax].DisplayText);
}
}
/**
* Called when a final response is received and its intent is parsed
*/
public void onIntentReceived(final String payload) {
System.out.println("--- Intent received by onIntentReceived() ---");
System.out.println(payload);
}
public void onPartialResponseReceived(final String response) {
System.out.println("--- Partial result received by onPartialResponseReceived() ---");
System.out.println(response);
}
public void onError(final int errorCode, final String response) {
System.err.println("--- Error received by onError() ---");
System.err.println("Error code: " + SpeechClientStatus.fromInt(errorCode) + " " + errorCode);
System.err.println("Error text: " + response);
}
/**
* Called when the microphone status has changed.
* #param recording The current recording state
*/
public void onAudioEvent(boolean recording) {
System.out.println("--- Microphone status change received by onAudioEvent() ---");
System.out.println("********* Microphone status: " + recording + " *********");
if (recording) {
System.out.println("Please start speaking.");
}
if (!recording) {
this.micClient.endMicAndRecognition();
}
}
}
You can clearly see that it is basically the same code but it gives me this error in the onError function after i call this.micClient.startMicAndRecognition();
:
Error code: LoginFailed -1910505470
I solved the problem.
I was getting a wrong Primary key in this line of code:
public String getPrimaryKey() {
return Integer.toString(R.string.primaryKey);
}
It is because the primary key is too long to be read by the processor as an integer then transformed to a String. I had to get the primary key from the main activity via getString(R.string.primaryKey) then pass it as a parameter to the constructor of the SpeechRecognition class.
I downloaded the code from the below link for android which has option to send echo, broadcast as well as user notification message.
Link for Android
Then I tried to use many codes for server side without any success so I studied and wrote the below code for the server side which I have hosted on Tomcat Server and there is a servlet which activates the client.
public class CcsServlet extends HttpServlet
{
private static Logger logger = Logger.getLogger(CcsServlet.class.getName());
private static final String USERNAME = "855350893195" + "#gcm.googleapis.com";
private static final String PASSWORD = "AIzaSyA9DQTcggUtABVC9lnV_Xb5VEQ8iKBEaP4";//AIzaSyAxDKiYUkU8EvtOgaCeZMypVJpzcYgyjhw-server
public void init(ServletConfig config) throws ServletException
{
SmackCcsClient ccsManager = SmackCcsClient.getInstance();
try
{
ccsManager.connect(USERNAME,PASSWORD);
}
catch (Exception e)
{
logger.warning("Cannot connect to CCS server.");
e.printStackTrace();
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException{
}
}
Code for the SmackCcsClient
public class SmackCcsClient {
private static Logger log = LoggerFactory.getLogger(SmackCcsClient.class);
private static SmackCcsClient sInstance = null;
XMPPConnection connection;
ConnectionConfiguration config;
public SmackCcsClient() {
// Add GcmPacketExtension
ProviderManager.getInstance().addExtensionProvider(
GCMConstants.GCM_ELEMENT_NAME,
GCMConstants.GCM_NAMESPACE,
new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json);
return packet;
}
});
}
/**
* Returns a random message id to uniquely identify a message.
*
*/
public String getRandomMessageId() {
return UUID.randomUUID().toString();
}
/**
* Sends a downstream GCM message.
*/
public void send(String jsonRequest) {
Packet request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendPacket(request);
}
public static SmackCcsClient getInstance()
{
if (sInstance == null)
sInstance = new SmackCcsClient();
return sInstance;
}
/**
* Handles an upstream data message from a device application.
*
* <p>
* This sample echo server sends an echo message back to the device.
* Subclasses should override this method to process an upstream message.
*/
public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message.
String category = jsonObject.get("category").toString();
// Use the packageName as the collapseKey in the echo packet
String collapseKey = "echo:CollapseKey";
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
payload.put("ECHO", "Application: " + category);
// Send an ECHO response back
String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
send(echo);
}
/**
* Handles an ACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle ACKS.
*/
public void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
log.debug("handleAckReceipt from: {}, messageId: {}", from, messageId);
}
/**
* Handles a NACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle NACKS.
*/
public void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
log.debug("handleNackReceipt() from: {}, messageId: {}",from, messageId);
}
/**
* Creates a JSON encoded GCM message.
*
* #param to
* RegistrationId of the target device (Required).
* #param messageId
* Unique messageId for which CCS will send an "ack/nack"
* (Required).
* #param payload
* Message content intended for the application. (Optional).
* #param collapseKey
* GCM collapse_key parameter (Optional).
* #param timeToLive
* GCM time_to_live parameter (Optional).
* #param delayWhileIdle
* GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId,
Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received from
* an application.
*
* #param to
* RegistrationId of the device who sent the upstream message.
* #param messageId
* messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
public static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param username
* GCM_SENDER_ID#gcm.googleapis.com
* #param password
* API Key
* #throws XMPPException
*/
public void connect(String username, String password) throws XMPPException {
config = new ConnectionConfiguration(GCMConstants.GCM_SERVER, GCMConstants.GCM_PORT);
config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true);
config.setRosterLoadedAtLogin(false);
config.setSendPresence(false);
config.setSocketFactory(SSLSocketFactory.getDefault());
// NOTE: Set to true to launch a window with information about packets
// sent and received
config.setDebuggerEnabled(true);
// -Dsmack.debugEnabled=true
XMPPConnection.DEBUG_ENABLED = true;
connection = new XMPPConnection(config);
connection.connect();
//**code used to send downstream message**
String toRegId = "APA91bECAx_1rzceJbPwas5FyRGPmpYFvWSJ0hfUlT30DSsrsXmBC5W-VmYTI0p8Gg9VZh598iNBcVki-H1dloBnaETfvZlJUZskTxHKE7DoDAgapH_X_DJE3toyZwXS4SHIdt5lYLty6OU_tmAOTZN4n88jlmSaR2_MrNmY1HuAMlddMfqAr10";
String messageId = sInstance.getRandomMessageId();
Map<String, String> payload = new HashMap<String, String>();
payload.put("Hello", "World");
payload.put("CCS", "Dummy Message");
payload.put("EmbeddedMessageId", messageId);
String collapseKey = "sample";
Long timeToLive = 10000L;
Boolean delayWhileIdle = true;
sInstance.send(createJsonMessage(toRegId, messageId, payload,
collapseKey, timeToLive, delayWhileIdle));
connection.addConnectionListener(new ConnectionListener() {
#Override
public void reconnectionSuccessful() {
log.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
log.info( "Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
log.info( "Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
log.info( "Connection closed on error.");
}
#Override
public void connectionClosed() {
log.info("Connection closed.");
}
});
// Handle incoming packets
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
log.info( "Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage.getExtension(GCMConstants.GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject = (Map<String, Object>) JSONValue
.parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
handleIncomingDataMessage(jsonObject);
// Send ACK to CCS
String messageId = jsonObject.get("message_id")
.toString();
String from = jsonObject.get("from").toString();
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else {
// logger.log(Level.WARNING,
// "Unrecognized message type (%s)",
messageType.toString();
}
} catch (Exception e) {
// logger.log(Level.SEVERE, "Couldn't send echo.", e);
}
}
}, new PacketTypeFilter(Message.class));
// Log all outgoing packets
connection.addPacketWriterInterceptor(new PacketInterceptor() {
#Override
public void interceptPacket(Packet packet) {
log.info( "Sent: {0}", packet.toXML());
}
}, new PacketTypeFilter(Message.class));
connection.login(username, password);
}
}
Code for GcmPacketExtension
class GcmPacketExtension extends DefaultPacketExtension {
String json;
public GcmPacketExtension(String json) {
super(GCMConstants.GCM_ELEMENT_NAME, GCMConstants.GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>", GCMConstants.GCM_ELEMENT_NAME,
GCMConstants.GCM_NAMESPACE, json, GCMConstants.GCM_ELEMENT_NAME);
}
#SuppressWarnings("unused")
public Packet toPacket() {
return new Message() {
// Must override toXML() because it includes a <body>
#Override
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<message");
if (getXmlns() != null) {
buf.append(" xmlns=\"").append(getXmlns()).append("\"");
}
if (getDefaultLanguage() != null) {
buf.append(" xml:lang=\"").append(getDefaultLanguage())
.append("\"");
}
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"")
.append(StringUtils.escapeForXML(getTo()))
.append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"")
.append(StringUtils.escapeForXML(getFrom()))
.append("\"");
}
buf.append(">");
buf.append(GcmPacketExtension.this.toXML());
buf.append("</message>");
return buf.toString();
}
};
}
}
Code for GcmConstants
public class GCMConstants {
public static final String GCM_SERVER = "gcm.googleapis.com";
public static final int GCM_PORT = 5235;
public static final String GCM_ELEMENT_NAME = "gcm";
public static final String GCM_NAMESPACE = "google:mobile:data";
}
Now when I try to send a downstream message using the code in connect that does not reach the device which device id I have given or Echo message from device also does not echoed by server.
Is it required to sign up thing for CCS required because I have already signed up, however did not get any response.
Second question is why neither of the communication is working.
Now I have cleared up all the issues and now my servlet is running and making a connection however it is returning "No Response from the server", which I am getting as exception while connecting.
when taking the code below for sending a message to a device with using smack libary 3.3.0
Getting an error
SASL authentication PLAIN failed: text:
at org.jivesoftware.smack.SASLAuthentication.authenticate(SASLAuthentication.java:342)
at org.jivesoftware.smack.XMPPConnection.login(XMPPConnection.java:221)
at org.jivesoftware.smack.Connection.login(Connection.java:366)
at com.test.xmpp.SmackCcsClient.connect(SmackCcsClient.java:335)
at com.test.xmpp.SmackCcsClient.main(SmackCcsClient.java:345)
When switching to HTTP server side, the messaging is working
/**
* Sample Smack implementation of a client for GCM Cloud Connection Server.
*
* <p>For illustration purposes only.
*/
public class SmackCcsClient {
Logger logger = Logger.getLogger("SmackCcsClient");
public static final String GCM_SERVER = "gcm.googleapis.com";
public static final int GCM_PORT = 5235;
public static final String GCM_ELEMENT_NAME = "gcm";
public static final String GCM_NAMESPACE = "google:mobile:data";
static Random random = new Random();
XMPPConnection connection;
ConnectionConfiguration config;
/**
* XMPP Packet Extension for GCM Cloud Connection Server.
*/
class GcmPacketExtension extends DefaultPacketExtension {
String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
}
#SuppressWarnings("unused")
public Packet toPacket() {
return new Message() {
// Must override toXML() because it includes a <body>
#Override
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<message");
if (getXmlns() != null) {
buf.append(" xmlns=\"").append(getXmlns()).append("\"");
}
if (getLanguage() != null) {
buf.append(" xml:lang=\"").append(getLanguage()).append("\"");
}
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
}
buf.append(">");
buf.append(GcmPacketExtension.this.toXML());
buf.append("</message>");
return buf.toString();
}
};
}
}
public SmackCcsClient() {
// Add GcmPacketExtension
ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
GCM_NAMESPACE, new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser)
throws Exception {
String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json);
return packet;
}
});
}
/**
* Returns a random message id to uniquely identify a message.
*
* <p>Note:
* This is generated by a pseudo random number generator for illustration purpose,
* and is not guaranteed to be unique.
*
*/
public String getRandomMessageId() {
return "m-" + Long.toString(random.nextLong());
}
/**
* Sends a downstream GCM message.
*/
public void send(String jsonRequest) {
Packet request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendPacket(request);
}
/**
* Handles an upstream data message from a device application.
*
* <p>This sample echo server sends an echo message back to the device.
* Subclasses should override this method to process an upstream message.
*/
public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message.
String category = jsonObject.get("category").toString();
// Use the packageName as the collapseKey in the echo packet
String collapseKey = "echo:CollapseKey";
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
payload.put("ECHO", "Application: " + category);
// Send an ECHO response back
String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
send(echo);
}
/**
* Handles an ACK.
*
* <p>By default, it only logs a INFO message, but subclasses could override it to
* properly handle ACKS.
*/
public void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId);
}
/**
* Handles a NACK.
*
* <p>By default, it only logs a INFO message, but subclasses could override it to
* properly handle NACKS.
*/
public void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId);
}
/**
* Creates a JSON encoded GCM message.
*
* #param to RegistrationId of the target device (Required).
* #param messageId Unique messageId for which CCS will send an "ack/nack" (Required).
* #param payload Message content intended for the application. (Optional).
* #param collapseKey GCM collapse_key parameter (Optional).
* #param timeToLive GCM time_to_live parameter (Optional).
* #param delayWhileIdle GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId, Map<String, String> payload,
String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received from an application.
*
* #param to RegistrationId of the device who sent the upstream message.
* #param messageId messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
public static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param username GCM_SENDER_ID#gcm.googleapis.com
* #param password API Key
* #throws XMPPException
*/
public void connect(String username, String password) throws XMPPException {
config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true);
config.setRosterLoadedAtLogin(false);
config.setSendPresence(false);
config.setSocketFactory(SSLSocketFactory.getDefault());
// NOTE: Set to true to launch a window with information about packets sent and received
config.setDebuggerEnabled(true);
// -Dsmack.debugEnabled=true
XMPPConnection.DEBUG_ENABLED = true;
connection = new XMPPConnection(config);
connection.connect();
connection.addConnectionListener(new ConnectionListener() {
#Override
public void reconnectionSuccessful() {
logger.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
logger.log(Level.INFO, "Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
logger.log(Level.INFO, "Connection closed on error.");
}
#Override
public void connectionClosed() {
logger.info("Connection closed.");
}
});
// Handle incoming packets
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
logger.log(Level.INFO, "Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket =
(GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject =
(Map<String, Object>) JSONValue.parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
handleIncomingDataMessage(jsonObject);
// Send ACK to CCS
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else {
logger.log(Level.WARNING, "Unrecognized message type (%s)",
messageType.toString());
}
} catch (ParseException e) {
logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
} catch (Exception e) {
logger.log(Level.SEVERE, "Couldn't send echo.", e);
}
}
}, new PacketTypeFilter(Message.class));
// Log all outgoing packets
connection.addPacketInterceptor(new PacketInterceptor() {
#Override
public void interceptPacket(Packet packet) {
logger.log(Level.INFO, "Sent: {0}", packet.toXML());
}
}, new PacketTypeFilter(Message.class));
connection.login(username, password);
}
public static void main(String [] args) {
final String userName = "124079202908" + "#gcm.googleapis.com";
final String password = "AIzaSyAMt1y_xILk72wiQybuqVEdiq7uGXBEUhQ";
SmackCcsClient ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
}
}
Also I submitted a request for using the API over a week ago and didn't recieve a reply yet.
The problem is resolved. The issue was indeed that the project wasn't registered. I received an email from Google 2 weeks after asking for the API receiving confirmation.
The error in this case really should be improved and it would be nice if Google wrote approximately how long to receive the email.
Just wanted to update anyone else encountering the problem.
#user2679041 i guess you are saying right. I have register my gcm project id just today and i haven't receive any confirmation email yet. That's really annoying, who else is going to wait for a 2 weeks? I have saw few queries # stackoverflow that people says there request approved by google after 3 months too.
I have followed the this link
and its has a note saying :
After you have created your GCM enabled project in the API Console you must fill out this form and become a trial partner to use upstream messaging and user notifications via CCS. Access is limited to those that fill out the form. You will receive an email from Google informing you that you now have access; Google will also send you the address of an echo server that you can use to bounce messages back to your application.
So we might say Google has released beta version with limited no. of access.