QuickBlox notifications: push token disappears - android

I've been working on an Android app that uses QuickBlox for chatting. The chat is working fine, but I am encountering difficulties with the push notifications for logged out users.
The problem is that some devices sometimes stop receiving notifications. It often works fine for a couple of hours, but then suddenly completely stops working for that device. A full manual uninstall + reinstall (which then logs the user in again to QuickBlox + GCM) usually solves the problem.
Looking at the QuickBlox admin panel, I can see that some subscriptions lose their push token.
User 1 registered on device A. Push token has disappeared.
http://i.stack.imgur.com/YUkyw.png
User 1 registered on device B. Push token still present.
http://i.stack.imgur.com/Yu5IQ.png
I register with GCM + QuickBlox push messages once the user is logged in to QuickBlox. This code was largely taken from the QuickBlox chat sample.
ChatService:
private void loginToChat(final Context context, final QBUser user, final QBEntityCallback callback)
{
this.qbChatService.login(user, new QBEntityCallbackImpl()
{
#Override
public void onSuccess()
{
checkPlayServices(context);
try
{
qbChatService.startAutoSendPresence(AUTO_PRESENCE_INTERVAL_IN_SECONDS);
}
catch (SmackException.NotLoggedInException e)
{
e.printStackTrace();
}
if (callback != null)
{
callback.onSuccess();
}
}
#Override
public void onSuccess(Object result, Bundle params)
{
super.onSuccess(result, params);
checkPlayServices(context);
}
#Override
public void onError(List errors)
{
if (callback != null)
{
callback.onError(errors);
}
}
});
}
private void checkPlayServices(Context context)
{
// Register with GCM after the session has been created
PlayServicesHelper playServicesHelper = new PlayServicesHelper(context);
playServicesHelper.checkPlayServices();
}
PlayServicesHelper:
public class PlayServicesHelper
{
private static final String PROPERTY_APP_VERSION = "appVersion";
private static final String PROPERTY_REG_ID = "registration_id";
private static final String TAG = "PlayServicesHelper";
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
private GoogleCloudMessaging googleCloudMessaging;
private Context activity;
private String regId;
private String projectNumber;
public PlayServicesHelper(Context activity)
{
this.activity = activity;
this.projectNumber = activity.getString(R.string.gcm_project_number);
checkPlayService();
}
private void checkPlayService()
{
// Check device for Play Services APK. If check succeeds, proceed with
// GCM registration.
if (checkPlayServices())
{
googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
regId = getRegistrationId();
if (regId.isEmpty())
{
registerInBackground();
}
}
else
{
Log.i(TAG, "No valid Google Play Services APK found.");
}
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
public boolean checkPlayServices()
{
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
if (resultCode != ConnectionResult.SUCCESS)
{
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode))
{
GooglePlayServicesUtil.getErrorDialog(resultCode, activity, PLAY_SERVICES_RESOLUTION_REQUEST).show();
}
return false;
}
return true;
}
/**
* Gets the current registration ID for application on GCM service.
* <p/>
* If result is empty, the app needs to register.
*
* #return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId()
{
final SharedPreferences prefs = getGCMPreferences();
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty())
{
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersionCode();
if (registeredVersion != currentVersion)
{
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
/**
* Registers the application with GCM servers asynchronously.
* <p/>
* Stores the registration ID and app versionCode in the application's
* shared preferences.
*/
private void registerInBackground()
{
new AsyncTask<Void, Void, String>()
{
#Override
protected String doInBackground(Void... params)
{
String msg = "";
try
{
if (googleCloudMessaging == null)
{
googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
}
regId = googleCloudMessaging.register(projectNumber);
msg = "Device registered, registration ID=" + regId;
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
Handler h = new Handler(activity.getMainLooper());
h.post(new Runnable()
{
#Override
public void run()
{
subscribeToPushNotifications(regId);
}
});
// Persist the regID - no need to register again.
storeRegistrationId(regId);
}
catch (IOException ex)
{
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg)
{
Log.i(TAG, msg + "\n");
}
}.execute(null, null, null);
}
/**
* #return Application's {#code SharedPreferences}.
*/
private SharedPreferences getGCMPreferences()
{
return activity.getSharedPreferences(activity.getPackageName(), Context.MODE_PRIVATE);
}
/**
* Subscribe to Push Notifications
*
* #param regId registration ID
*/
private void subscribeToPushNotifications(String regId)
{
String deviceId;
final TelephonyManager mTelephony = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null)
{
deviceId = mTelephony.getDeviceId();
}
else
{
deviceId = Settings.Secure.getString(activity.getContentResolver(), Settings.Secure.ANDROID_ID);
}
QBMessages.subscribeToPushNotificationsTask(regId, deviceId, QBEnvironment.PRODUCTION, new QBEntityCallbackImpl<ArrayList<QBSubscription>>()
{
#Override
public void onSuccess(ArrayList<QBSubscription> qbSubscriptions, Bundle bundle)
{
}
#Override
public void onError(List<String> strings)
{
}
});
}
/**
* Stores the registration ID and app versionCode in the application's
* {#code SharedPreferences}.
*
* #param regId registration ID
*/
private void storeRegistrationId(String regId)
{
final SharedPreferences prefs = getGCMPreferences();
int appVersion = getAppVersionCode();
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.apply();
}
private int getAppVersionCode()
{
try
{
PackageInfo packageInfo = this.activity.getPackageManager().getPackageInfo(this.activity.getPackageName(), 0);
return packageInfo.versionCode;
}
catch (PackageManager.NameNotFoundException e)
{
return 0;
}
}
}
To allow a user to receive notifications, I log him out every time the app goes to the background (using onStop in a BaseActivity) and then log back in when the app resumes (which then again starts the PlayServicesHelper).
ChatService.getInstance().logout(null);
The app also uses Parse (with GCM) for push notifications, but the problem still seemed to occur when I disabled Parse.
Could the problem be caused by having one user registered on multiple devices at the same time? Could the frequent app installs (without manually removing the previous installation) result in this behavior?

Push token can disappear if 2 users use the same device
for example, User 1 subscribed for pushes on deviceA
Then User 2 subscribed for pushes on deviceA (the same device)
After this only User 2 will be receiving pushes, not User 1
Is there a chance to have such logic in your case?

Related

GCM device is always unregistered

After a lot of searching and following this guide https://github.com/codepath/android_guides/wiki/Google-Cloud-Messaging
The response from my post requests is always returning the error below, despite the fact that my device has been registered. Also i don't know if that helps but i am using android studio in order to deploy my application.
{"multicast_id":xxxxx,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"NotRegistered"}]}
This is my code inside my first activity
private GCMClientManager mPushClientManager;
private final String PROJECT_NUMBER = "kkkkkk";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPushClientManager = new GCMClientManager(this, PROJECT_NUMBER);
mPushClientManager.registerIfNeeded(new GCMClientManager.RegistrationCompletedHandler() {
#Override
public void onSuccess(String registrationId, boolean isNewRegistration) {
Toast.makeText(ChoicesActivity.this, registrationId,
Toast.LENGTH_SHORT).show();
Log.e("test", "regId " + registrationId);
// SEND async device registration to your back-end server
// linking user with device registration id
// POST https://my-back-end.com/devices/register?user_id=123&device_id=abc
}
#Override
public void onFailure(String ex) {
super.onFailure(ex);
// If there is an error registering, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off when retrying.
}
});
}
This is my GCMClientManager
public class GCMClientManager {
// Constants
public static final String TAG = "GCMClientManager";
public static final String EXTRA_MESSAGE = "message";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
// Member variables
private GoogleCloudMessaging gcm;
private String regid;
private String projectNumber;
private Activity activity;
public static abstract class RegistrationCompletedHandler {
public abstract void onSuccess(String registrationId, boolean isNewRegistration);
public void onFailure(String ex) {
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
Log.e(TAG, ex);
}
}
public GCMClientManager(Activity activity, String projectNumber) {
this.activity = activity;
this.projectNumber = projectNumber;
this.gcm = GoogleCloudMessaging.getInstance(activity);
}
// Register if needed or fetch from local store
public void registerIfNeeded(final RegistrationCompletedHandler handler) {
if (checkPlayServices()) {
regid = getRegistrationId(getContext());
if (regid.isEmpty()) {
registerInBackground(handler);
} else { // got id from cache
Log.i(TAG, regid);
handler.onSuccess(regid, false);
}
} else { // no play services
Log.i(TAG, "No valid Google Play Services APK found.");
}
}
/**
* Registers the application with GCM servers asynchronously.
* <p>
* Stores the registration ID and app versionCode in the application's
* shared preferences.
*/
private void registerInBackground(final RegistrationCompletedHandler handler) {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(getContext());
}
InstanceID instanceID = InstanceID.getInstance(getContext());
regid = instanceID.getToken(projectNumber, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
Log.i(TAG, regid);
// Persist the regID - no need to register again.
storeRegistrationId(getContext(), regid);
} catch (IOException ex) {
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
handler.onFailure("Error :" + ex.getMessage());
}
return regid;
}
#Override
protected void onPostExecute(String regId) {
if (regId != null) {
handler.onSuccess(regId, true);
}
}
}.execute(null, null, null);
}
/**
* Gets the current registration ID for application on GCM service.
* <p>
* If result is empty, the app needs to register.
*
* #return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
/**
* Stores the registration ID and app versionCode in the application's
* {#code SharedPreferences}.
*
* #param context application's context.
* #param regId registration ID
*/
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = getGCMPreferences(context);
int appVersion = getAppVersion(context);
Log.i(TAG, "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.commit();
}
/**
* #return Application's version code from the {#code PackageManager}.
*/
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
private SharedPreferences getGCMPreferences(Context context) {
// This sample app persists the registration ID in shared preferences, but
// how you store the regID in your app is up to you.
return getContext().getSharedPreferences(context.getPackageName(),
Context.MODE_PRIVATE);
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getContext());
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, getActivity(),
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(TAG, "This device is not supported.");
}
return false;
}
return true;
}
private Context getContext() {
return activity;
}
private Activity getActivity() {
return activity;
}
}
And this is my GCMMessageHandler
public class GCMMessageHandler extends GcmListenerService {
public static final int MESSAGE_NOTIFICATION_ID = 435345;
#Override
public void onMessageReceived(String from, Bundle data) {
String message = data.getString("message");
createNotification(from, message);
}
// Creates notification based on title and body received
private void createNotification(String title, String body) {
Context context = getBaseContext();
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.mipmap.ic_launcher).setContentTitle(title)
.setContentText(body);
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(MESSAGE_NOTIFICATION_ID, mBuilder.build());
}
}
Also my AndroidManifest.xml has the following inside
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demo" >
<uses-permission android:name="android.permission.INTERNET" />
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- gcm -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.demo.gcm.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.demo.gcm.permission.C2D_MESSAGE" />
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.example.demo.gcmquickstart" />
</intent-filter>
</receiver>
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="test"
android:theme="#style/Theme.DesignDemo" >
<activity
android:name=".activities.LoginActivity"
android:label="#string/app_name" >
</activity>
<activity
android:name=".activities.FirstActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".notifications.GCMMessageHandler"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
</application>
</manifest>
And this is my script in which i am making the post requests
REGISTRATION_ID=xxxxxx
SERVER_KEY=xxxxx
curl --header "Authorization: key=$SERVER_KEY" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{ \"data\" : {\"foo\": \"bar\"}, \"registration_ids\":[\"$REGISTRATION_ID\"] }"
GCM response 'Not Registered' means following "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." from documentation. Check in what case you get this error, when app is uninstalled from device or your describe incorrect broadcastreceiver in AndroidManifest. You can test your GCM functionallity with my test gcm server. Hope, this help you.

ListView won't update onNewIntent()

I'm developing a quite simple application that receives GCM's push notifications. There are similar questions here in SO, but this is the only one I found that tries to update the ListView in the onNewIntent() hook, but it wasn't helpful.
The "business logic" should be:
If a notification is received when the application isn't in foreground, add it to the ListView (so, the ListView will have only one item).
If a notification is received when the application is in foreground, add it to the ListView (so, it will have n>1 items).
This is the Activity that holds the ListView:
package ar.edu.uca.ingenieria.notificaciones;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import ar.edu.uca.ingenieria.notificaciones.adapter.NotificacionesAdapter;
import ar.edu.uca.ingenieria.notificaciones.gcm.GcmIntentService;
import ar.edu.uca.ingenieria.notificaciones.model.Notificacion;
/**
* Main UI for the demo app.
*/
public class MainActivity extends Activity {
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
private static final String TAG = "GCM Demo";
/**
* Es el project number obtenido en la API Console, como se explica en "Getting Started."
*/
String senderId;
TextView mDisplay;
List<Notificacion> notificaciones;
private ListView notificacionesListView;
GoogleCloudMessaging gcm;
Context context;
String regid;
private NotificacionesAdapter notificacionesAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDisplay = (TextView) findViewById(R.id.display);
context = getApplicationContext();
senderId = getSenderId();
intentarRegistrarGooglePlayServices();
Notificacion notificacion = getNotificacionFromIntent();
this.notificaciones = new ArrayList<Notificacion>();
inicializarListView(notificacion);
}
private void inicializarListView(Notificacion notificacion) {
this.notificaciones.add(notificacion);
// Create the adapter to convert the array to views
notificacionesAdapter = new NotificacionesAdapter(this, this.notificaciones);
// Attach the adapter to a ListView
this.notificacionesListView = (ListView) findViewById(R.id.lista_notificaciones);
this.notificacionesListView.setAdapter(notificacionesAdapter);
}
private void agregarNotificacion(Notificacion notificacion) {
this.notificaciones.add(notificacion);
// this.notificacionesAdapter.add(notificacion);
this.notificacionesAdapter.notifyDataSetChanged();
}
/**
* Verifica si Google Play Services está instalado en el dispositivo. De ser así, intenta
* registrarse y guarda el regid obtenido.
*/
private void intentarRegistrarGooglePlayServices() {
// Check device for Play Services APK. If check succeeds, proceed with GCM registration.
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(this);
regid = getRegistrationId(context);
if (regid.isEmpty()) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
Log.i(TAG, "regid: " + this.regid);
}
private Notificacion getNotificacionFromIntent() {
String tituloNotificacion = (String) getIntent().getCharSequenceExtra(GcmIntentService.GCM_TITULO);
Log.d(TAG, "Título: " + tituloNotificacion);
String mensajeNotificacion = (String) getIntent().getCharSequenceExtra(GcmIntentService.GCM_MENSAJE);
Log.d(TAG, "Mensaje: " + mensajeNotificacion);
// TODO esto muestra "1/1/1970" si no viene la fecha...
long fechaMilisegundos = getIntent().getLongExtra(GcmIntentService.GCM_FECHA, new Date(0).getTime());
Date fechaNotificacion = new Date(fechaMilisegundos);
return new Notificacion(tituloNotificacion, mensajeNotificacion, fechaNotificacion);
}
#Override
protected void onResume() {
super.onResume();
// Check device for Play Services APK.
checkPlayServices();
Log.i(TAG, "regid: " + this.regid);
Log.d(TAG, "onResume");
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent");
this.setIntent(intent);
this.agregarNotificacion(this.getNotificacionFromIntent());
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(TAG, "This device is not supported.");
finish();
}
return false;
}
return true;
}
/**
* Stores the registration ID and the app versionCode in the application's
* {#code SharedPreferences}.
*
* #param context application's context.
* #param regId registration ID
*/
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = getGcmPreferences(context);
int appVersion = getAppVersion(context);
Log.i(TAG, "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.apply();
}
/**
* Gets the current registration ID for application on GCM service, if there is one.
* <p>
* If result is empty, the app needs to register.
*
* #return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGcmPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
/**
* Registers the application with GCM servers asynchronously.
* <p>
* Stores the registration ID and the app versionCode in the application's
* shared preferences.
*/
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(senderId);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device will send
// upstream messages to a server that echo back the message using the
// 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
}
#Override
protected void onDestroy() {
super.onDestroy();
}
/**
* #return Application's version code from the {#code PackageManager}.
*/
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
/**
* #return Application's {#code SharedPreferences}.
*/
private SharedPreferences getGcmPreferences(Context context) {
// This sample app persists the registration ID in shared preferences, but
// how you store the regID in your app is up to you.
return getSharedPreferences(MainActivity.class.getSimpleName(),
Context.MODE_PRIVATE);
}
// TODO implementar esto del lado del server
/**
* Sends the registration ID to your server over HTTP, so it can use GCM/HTTP or CCS to send
* messages to your app. Not needed for this demo since the device sends upstream messages
* to a server that echoes back the message using the 'from' address in the message.
*/
private void sendRegistrationIdToBackend() {
// Your implementation here.
}
// TODO: esto no es responsabilidad de esta clase...
// Tal vez un "setting provider" o similar
private String getSenderId() {
Resources res = getResources();
return res.getString(R.string.senderId);
}
}
However, although the agregarNotificacion() method is called with the correct notification, it won't update the ListView items.
I did quite a bit googling, and read that methods like myAdapter.add() should be used, but it didn't work either (that's why I left it commented).
So, in this particular situation (creating the ListView in one Activity hook, and updating it in another), how could I refresh my ListView?
Two final considerations:
There's a small bug: if the application is run for the first time, and a push is received then, it won't be added (since the getNotificacionFromIntent() method is returning an object with null fields, instead of a null Notificacion). I'm aware of it, but running and exiting the app is a quick possible workaround.
Sorry for the "spanglish": this is a university project, so I'm using Spanish names in the variables and classes. But also, there's some copy-paste involved, that I haven't refactored yet.
Any help or direction will be highly appreciated.
Regards.
The solution was to extend from ListActivity, and sending the new items via: this.notificacionesAdapter.add(newItem).

Android Google Play Services returns invalid token for blogger api

I've been given access to the blogger API, I've confirmed that on my developer console.
I've also written some code to perform oAuth2 with Google Play Services using some of the code below.
String SCOPE ="oauth2:https://www.googleapis.com/auth/blogger";
GoogleAuthUtil.getToken(context, "myEmail#gmail.com", mScope);
It returns a token. As it should.
However, once I try to access the api using the token i get a error.
Unexpected response code 403 for https://www.googleapis.com/blogger/v3/users/self/blogs
Here is my request:
And here is my response:
Here is my BaseActivity.java code that gets the token:
public class BaseActivity extends Activity {
static final int REQUEST_CODE_PICK_ACCOUNT = 1000;
static final int REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR = 1001;
static final int REQUEST_CODE_RECOVER_FROM_AUTH_ERROR = 1002;
private static final String SCOPE ="oauth2:https://www.googleapis.com/auth/blogger";
private String mEmail; // Received from newChooseAccountIntent(); passed to getToken()
public ProgressDialog mDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDialog = new ProgressDialog(this);
login();
}
public void login() {
pickUserAccount();
}
private void pickUserAccount() {
String[] accountTypes = new String[]{"com.google"};
Intent intent = AccountPicker.newChooseAccountIntent(null, null, accountTypes, false, null, null, null, null);
startActivityForResult(intent, REQUEST_CODE_PICK_ACCOUNT);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PICK_ACCOUNT) {
// Receiving a result from the AccountPicker
if (resultCode == RESULT_OK) {
mEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
// With the account name acquired, go get the auth token
getToken();
} else if (resultCode == RESULT_CANCELED) {
// The account picker dialog closed without selecting an account.
// Notify users that they must pick an account to proceed.
Toast.makeText(this, "Pick Account", Toast.LENGTH_SHORT).show();
}
} else if ((requestCode == REQUEST_CODE_RECOVER_FROM_AUTH_ERROR ||
requestCode == REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR)
&& resultCode == RESULT_OK) {
// Receiving a result that follows a GoogleAuthException, try auth again
getToken();
}
}
private void getToken() {
if (mEmail == null) {
pickUserAccount();
} else {
if (isDeviceOnline()) {
new getTokenTask(BaseActivity.this, mEmail, SCOPE).execute();
} else {
Toast.makeText(this, "Not online", Toast.LENGTH_LONG).show();
}
}
}
/**
* This method is a hook for background threads and async tasks that need to
* provide the user a response UI when an exception occurs.
*/
public void handleException(final Exception e) {
// Because this call comes from the AsyncTask, we must ensure that the following
// code instead executes on the UI thread.
runOnUiThread(new Runnable() {
#Override
public void run() {
if (e instanceof GooglePlayServicesAvailabilityException) {
// The Google Play services APK is old, disabled, or not present.
// Show a dialog created by Google Play services that allows
// the user to update the APK
int statusCode = ((GooglePlayServicesAvailabilityException)e).getConnectionStatusCode();
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(statusCode, BaseActivity.this, REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
dialog.show();
} else if (e instanceof UserRecoverableAuthException) {
// Unable to authenticate, such as when the user has not yet granted
// the app access to the account, but the user can fix this.
// Forward the user to an activity in Google Play services.
Intent intent = ((UserRecoverableAuthException)e).getIntent();
startActivityForResult(intent, REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR);
}
}
});
}
public boolean isDeviceOnline() {
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
return true;
} else {
return false;
}
}
public class getTokenTask extends AsyncTask{
Activity mActivity;
String mScope;
String mEmail;
getTokenTask(Activity activity, String name, String scope) {
this.mActivity = activity;
this.mScope = scope;
this.mEmail = name;
}
#Override
protected Object doInBackground(Object[] params) {
try {
String token = fetchToken();
Preferences.saveString(Constants.KEY_BLOGGER_TOKEN, token);
} catch (IOException e) {
// The fetchToken() method handles Google-specific exceptions,
// so this indicates something went wrong at a higher level.
// TIP: Check for network connectivity before starting the AsyncTask.
}
return null;
}
/**
* Gets an authentication token from Google and handles any
* GoogleAuthException that may occur.
*/
protected String fetchToken() throws IOException {
try {
return GoogleAuthUtil.getToken(mActivity, mEmail, mScope);
} catch (UserRecoverableAuthException userRecoverableException) {
// GooglePlayServices.apk is either old, disabled, or not present
// so we need to show the user some UI in the activity to recover.
((BaseActivity)mActivity).handleException(userRecoverableException);
} catch (GoogleAuthException fatalException) {
// Some other type of unrecoverable exception has occurred.
// Report and log the error as appropriate for your app.
}
return null;
}
}
}
I've been banging my head against the wall on this one. Anyone have any ideas?
Finally figured it out.
My build.gradle file somehow ended up having a different Application ID than my manifest. I changed it so they both match the manifest, and boom! it worked.

Android root activity sometimes started spontaneously without any user actions

I have a strange issue which I cannot explain. In the manifest file the launch activity of my app is defined as follows:
<activity
android:name="com.xxx.xxx.xxx.StartupActivity"
android:label="#string/app_name"
android:theme="#android:style/Theme.Light.NoTitleBar"
android:screenOrientation="sensorPortrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
In the StartUpActivity the following check is performed:
protected void startIntent() {
Intent intent;
if (checkCurrentProfile()) {
Notifier.showHttpToast(R.string.toastLoggedIn);
//try to update the device token in the database
Request.updateDeviceToken();
intent = new Intent(this, GameListActivity.class);
} else {
intent = new Intent(this, RegisterActivity.class);
}
startActivity(intent);
finish();
}
So if the user has a valid account the GameListActivity is shown as root activity:
<activity
android:name="com.xxx.xxx.xxx.xxx.GameListActivity"
android:label="#string/app_name"
android:theme="#style/MyTheme"
android:screenOrientation="sensorPortrait" >
</activity>
The issue now is the following: sometimes the system brings the root activity to the front spontaneously without any user actions. It only occurs sometimes, but I can't figure out the cause. Can anyone help me out here?
The StartUpActivity looks like this:
public class StartupActivity extends StartupCoreActivity implements OnRegisterGCMListener {
private IabHelper mHelper;
private IabHelper.QueryInventoryFinishedListener mQueryInventoryFinishedListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerOnRegisterGCMListener(this);
if (!registerGCMneeded()) {
initInAppBilling();
}
}
#Override
public void registerGCMfinished() {
initInAppBilling();
}
private void initInAppBilling() {
boolean hasPremium = Prefs.getBoolValue(getResources().getString(R.string.pref_key_upgrade_premium), false);
if (hasPremium) {
//unlocking contents not needed
startIntent();
} else {
Prefs.storeValue(getResources().getString(R.string.pref_key_upgrade_premium), false);
mHelper = new IabHelper(this, C.Billing.BILLING_KEY);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (result.isSuccess()) {
queryInventory();
} else {
startIntent();
}
}
});
}
}
private void queryInventory() {
String[] products = {C.Billing.ITEM_SKU};
mQueryInventoryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
if (result.isSuccess()) {
checkPremiumVersion(inv);
} else {
startIntent();
}
}
};
mHelper.queryInventoryAsync(true, Arrays.asList(products), mQueryInventoryFinishedListener);
}
private void checkPremiumVersion(Inventory inv) {
if (inv.hasPurchase(C.Billing.ITEM_SKU)) {
Request.updPremiumVersion();
Prefs.storeValue(getResources().getString(R.string.pref_key_upgrade_premium), true);
Notifier.showHttpToast(R.string.toastPremiumContentsUnlocked);
}
startIntent();
}
}
And the StartupCoreActivity looks like this:
public class StartupCoreActivity extends Activity {
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = xxxx;
GoogleCloudMessaging mGcm;
Context mContext;
String mRegId;
/**
* Substitute you own sender ID here. This is the project number you got
* from the API Console, as described in "Getting Started."
*/
String SENDER_ID = "xxxxxxxxxx";
private OnRegisterGCMListener mOnRegisterGCMListener = null;
public void registerOnRegisterGCMListener(OnRegisterGCMListener listener) {
mOnRegisterGCMListener = listener;
}
protected boolean registerGCMneeded() {
mContext = getApplicationContext();
// Check device for Play Services APK.
if (checkPlayServices()) {
// If this check succeeds, proceed with normal processing.
// Otherwise, prompt user to get valid Play Services APK.
mGcm = GoogleCloudMessaging.getInstance(this);
mRegId = getRegistrationId(mContext);
if (mRegId.isEmpty()) {
registerInBackground();
return true;
} else {
// note we never called setContentView()
return false;
}
}
return false;
}
protected void startIntent() {
Intent intent;
if (checkCurrentProfile()) {
Notifier.showHttpToast(R.string.toastLoggedIn);
//try to update the device token in the database
Request.updateDeviceToken();
intent = new Intent(this, GameListActivity.class);
} else {
intent = new Intent(this, RegisterActivity.class);
}
startActivity(intent);
finish();
}
private boolean checkCurrentProfile() {
KUPlayer me = G.getMySelf();
if (me.getPlayerId() <= 0) {
//database was not present yet and has been created
//or database was present, but profile cannot be read anymore
// make sure Login screen appears ONLY if PlayerID cannot be retrieved anymore
if (G.getPlayerID() <=0) {
Prefs.storeValue(Prefs.PREF_KEY_PWD_SAVED, false);
return false;
}
}
return true;
}
// You need to do the Play Services APK check here too.
#Override
protected void onResume() {
super.onResume();
checkPlayServices();
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
finish();
}
return false;
}
return true;
}
/**
* Registers the application with mGcm servers asynchronously.
* <p>
* Stores the registration ID and the app versionCode in the application's
* shared preferences.
*/
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (mGcm == null) {
mGcm = GoogleCloudMessaging.getInstance(mContext);
}
mRegId = mGcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + mRegId;
// Persist the mRegId - no need to register again.
storeRegistrationId(mContext, mRegId);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
// note we never called setContentView()
if (mOnRegisterGCMListener != null) {
mOnRegisterGCMListener.registerGCMfinished();
}
}
}.execute(null, null, null);
}
/**
* #return Application's version code from the {#code PackageManager}.
*/
private static int getAppVersionCode(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
private static String getAppVersionName(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionName;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
/**
* Stores the registration ID and the app versionCode in the application's
* {#code SharedPreferences}.
*
* #param mContext application's mContext.
* #param mRegId registration ID
*/
private void storeRegistrationId(Context context, String regId) {
Prefs.storeValue(Prefs.PREF_KEY_DEVICE_TOKEN, regId);
Prefs.storeValue(Prefs.PREF_KEY_APP_VERSION_CODE, getAppVersionCode(context));
Prefs.storeValue(Prefs.PREF_KEY_APP_VERSION_NAME, getAppVersionName(context));
AppRate.resetAfterUpdate();
}
/**
* Gets the current registration ID for application on mGcm service, if there is one.
* <p>
* If result is empty, the app needs to register.
*
* #return registration ID, or empty string if there is no existing
* registration ID.
*/
private String getRegistrationId(Context context) {
String registrationId = Prefs.getStringValue(Prefs.PREF_KEY_DEVICE_TOKEN);
if (registrationId.isEmpty()) {
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing mRegId is not guaranteed to work with the new
// app version.
int registeredVersion = Prefs.getIntValue(Prefs.PREF_KEY_APP_VERSION_CODE);
int currentVersion = getAppVersionCode(context);
if (registeredVersion != currentVersion) {
return "";
}
return registrationId;
}
}
I guess it has to do with the following known Android bug:
How to prevent multiple instances of an activity when it is launched with different intents
And as suggested put the following code in the onCreate method of the rootActivity:
// Possible work around for market launches. See http://code.google.com/p/android/issues/detail?id=2373
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
final Intent intent = getIntent();
final String intentAction = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null && intentAction.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
}
I tested it and when you start your app from Google Play store in stead of launch screen, then indeed finish() is called from above code.

how to resolve Android gcm notifications duplicate messages

Hi am getting duplicate notification from gcm server , how to resolve it
public class RegisterActivity{
private GoogleCloudMessaging gcm;
Context context;
String regId;
private static final String REG_ID = "regId";
private static final String APP_VERSION = "appVersion";
static final String TAG = "Register Activity";
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
private GCMRegistrationCallBack registeredActivity;
private Error error;
public void registerGCM(Context _context)
{
context = _context;
if(!checkPlayServices()){
Log.e(TAG, "This device is not supported.");
return;
}
registeredActivity = (GCMRegistrationCallBack) _context;
gcm = GoogleCloudMessaging.getInstance(context);
regId = getRegistrationId(context);
if(TextUtils.isEmpty(regId))
{
registerInBackground();
Log.d("RegisterActivity",
"registerGCM - successfully registered with GCM server - regId: "
+ regId);
}
else
{
registeredActivity.didFinishRegisteringWithGCM(regId);
}
}
private String getRegistrationId(Context context)
{
final SharedPreferences prefs = context.getSharedPreferences(
GCMRegistrationCallBack.class.getSimpleName(), Context.MODE_PRIVATE);
String registrationId = prefs.getString(REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
int registeredVersion = prefs.getInt(APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
Log.d("RegisterActivity",
"I never expected this! Going down, going down!" + e);
throw new RuntimeException(e);
}
}
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regId = gcm.register(Config.GOOGLE_PROJECT_ID);
storeRegistrationId(context, regId);
}
catch(UnknownHostException _exception)
{
error = new Error("UnknownHostException");
callErrorCallBackOnMainThread();
}
catch(SocketTimeoutException _exception)
{
error = new Error("SocketTimeoutException");
callErrorCallBackOnMainThread();
}
catch (IOException _exception) {
error = new Error(_exception.getMessage());
callErrorCallBackOnMainThread();
}
catch(Exception _exception)
{
error = new Error("unknown exception");
callErrorCallBackOnMainThread();
}
return regId;
}
#Override
protected void onPostExecute(String msg) {
if(!TextUtils.isEmpty(msg))
registeredActivity.didFinishRegisteringWithGCM(regId);
}
}.execute(null, null, null);
}
private void callErrorCallBackOnMainThread()
{
Activity regAvtivity = (Activity)registeredActivity;
regAvtivity.runOnUiThread(new Runnable()
{
public void run()
{
registeredActivity.didFailedToReceiveRegistrationId(error);
}
});
}
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = context.getSharedPreferences(
GCMRegistrationCallBack.class.getSimpleName(), Context.MODE_PRIVATE);
int appVersion = getAppVersion(context);
Log.i(TAG, "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(REG_ID, regId);
editor.putInt(APP_VERSION, appVersion);
editor.commit();
}
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, (Activity)context,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(TAG, "This device is not supported.");
}
return false;
}
return true;
}
}
Above code for getting registration number please correct me anything wrong... but some time am getting duplicate notification.
thanks
You should use Canonical IDs for this. This might happen because you registered with several IDs for the same device. Using canonical IDs will set your ID to be the last registration you've made.
As per the GCM reference about this:
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.
More info here.

Categories

Resources