Is there anyways in sending a location based push notification for android devices with out using a third party push notification service such as Parse? I would like to send push notification to my users without the annoyance of getting a notification that doesn't relate to that specific user because they are not in a certain area. Also, I could get the users location based on a time interval but I would rather do it a different way then that, if possible.
Yes, this is entirely possible, as long as I'm correctly interpreting what you are asking.
To accomplish this, you would send the GCM push notification to all of your users (unless you had a way, server-side of filtering some of them out). Then in your application, instead of just creating a Notification and passing it to the notification manager, you would first use the LocationManager (or the newer LocationServices API) to determine if the user is within the proper location first, and then just discard the GCM notification if they're not.
You'll need to take care of several things in order to do this:
Your AndroidManifest.xml will require several permission changes, both for the GCM changes, and for the Location access:
<!-- Needed for processing notifications -->
<permission android:name="com.myappname.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.myappname.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- Needed for Location -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
You'll also need to set up a Notification Receiver in the <application> section of the manifest:
<receiver android:name="com.myappname.NotificationReceiver" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.myappname" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.myappname" />
</intent-filter>
</receiver>
In addition, you'll need write your NotificationReceiver java class, and override the onReceive function:
public class NotificationReceiver extends BroadcastReceiver {
public void onReceive(final Context context, final Intent intent) {
if ("com.google.android.c2dm.intent.REGISTRATION".equals(intent.getAction())) {
handleRegistration(context, intent); // you'll have to write this function
} else if ("com.google.android.c2dm.intent.RECEIVE".equals(intent.getAction())) {
// the handle message function will need to check the user's current location using the location API you choose, and then create the proper Notification if necessary.
handleMessage(context, intent);
}
}
Related
I have been reading various tutorials, other SO threads, as well as the official Android Developer and Firebase documentation to no avail. I've tried nearly everything and I'm running out of steam as well as time as I'm in the process of repairing a notification system that previously worked but no longer works.
I am using Azure Notification Hubs to distribute notifications to FCM among other Push Notification platforms. My FCM project targets only Android. My app is built in Xamarin.Forms using the latest NuGet package versions of all dependencies (Xamarin.Forms 5.x.x.x, Firebase.Messaging 122.0, etc.).
Currently, remote messages received while the app is running or backgrounded work flawlessly via a custom Service inheriting and implementing FirebaseMessagingService. Once the app is killed (task switcher -> swipe app away), upon sending further messages I start seeing Logcat messages with the following:
broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg= (has extras) }
I understand Google changed the way implicit receivers work in API 26 and above and my understanding is that this action (com.google.android.c2dm.intent.RECEIVE) is not included in the exception list, so I am lost as to how the messages can be listened for and handled in the background. I have read in other threads as recently as July 2019 that FCM messages received while an app is killed are supposed to be sent directly to the notification tray. This cannot be a widespread problem as many applications send notifications while killed, so I'm hoping to get some current information to direct me to a solution.
Is this intent broadcast being cancelled because of the implicit receiver changes, or am I doing something else wrong?
I am testing on a OnePlus 7 Pro with Android 10, so I am wondering if maybe it's a battery optimization issue that others have mentioned on devices by OEMs such as Huawei and Xiaomi.
My app is targeting Android API level 29 with min API 21
I have enabled Direct Boot Awareness for my main activity as well as the receiver to ensure that the receiver intercepts intents for my app upon boot and before the user has opened the app:
<receiver android:directBootAware="true" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND" android:name=".NotificationReceiver">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="<package>" />
</intent-filter>
</receiver>
My main activity includes intent filters for being the launch activity:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name=".MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
I request the following permissions:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
I have defined the following meta-data tags in my manifest <application> tag:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/..."/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/..." />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/..." />
These values work perfectly for received notifications while in background, so I know they are correctly configured.
Edit 1:
I have done more digging and debugging since posting and I do see that, without my custom BroadcastReceiver also listening for the c2dm.intent.RECEIVE action that my app:
is in fact getting remote messages I send while the app is killed via the Google-provided internal FirebaseInstanceIdReceiver that is part of the documented FCM setup (I removed the NotificationReceiver service mentioned prior)
is starting my custom implementation of the FirebaseMessagingService which (see below screenshot)
is not triggering OnMessageReceived overload in my FirebaseMessagingService (see below screenshot)
logcat upon receiving FCM remote message while app is killed
*******FirebaseService partial code:
[Service(DirectBootAware = true, Exported = true, Enabled = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class MyFirebaseService : FirebaseMessagingService
{
public MyFirebaseService()
{
}
public override ComponentName StartService(Intent service)
{
Log.Info("GCM", $"MyFirebaseService started from intent {service}");
return base.StartService(service);
}
public override void OnMessageReceived(RemoteMessage message)
{
var notification = message.GetNotification();
Log.Info("GCM", $"Received remote message from FCM. Has notification: {notification != null}, has data: {message.Data != null}");
if (notification != null)
{
message.Data.TryGetValue("route", out string route);
SendNotification(notification.Title, notification.Body, route);
}
else
{
ParseDataNotification(message);
}
}
...
I was able to resolve the issue. My FirebaseMessagingService implementation had a Dependency Injection call in the constructor which failed when the service was started in the background by the FirebaseIidInstanceReceiver. This caused the service to fail to start and did not generate Android notifications while the app was killed.
Since I've done a lot of digging and information on this topic is so fragmented and out of date, I'll try to compile what I know results in a working solution here:
Follow the steps here, notably setting up your FCM project and downloading the google-services.json file.
Ensure your manifest declares the following permissions:
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
Add the following within your AndroidManifest <application> tag to listen for message receives:
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
Optionally define defaults for notification channel, notification icon (must be white color only, allowing transparency), and notification icon color when the notification tray is expanded, also within the <application> manifest tag:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="#string/..."/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/..." />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/..." />
Create a custom class inheriting from FirebaseMessagingService. In Xamarin.Forms, you will need the Xamarin.Firebase.Messaging NuGet package for this class. Within your implementation, you should override OnMessageReceived(RemoteMessage) and add your application logic which will handle messages containing the notification property in the foreground and messages with only the data property in both the foreground and background. Your class should be decorated with the following attributes (note that DirectBootAware is optional; see below):
[Service(DirectBootAware = true, Exported = true, Enabled = true)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
If you wish to ensure that notifications can be received after a device reboot and before the device is unlocked, you may consider making your application and your FirebaseMessagingService implementation Direct Boot Aware (more here)
In your MainActivity, ensure a Notification Channel is created for devices running Android O or higher, and this method invoked at some point during OnCreate:
private void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channelId = GetString(Resource.String./*res id here*/);
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
// Don't re-create the notification channel if we already created it
if (notificationManager.GetNotificationChannel(channelId) == null)
{
var channel = new NotificationChannel(channelId,
"<display name>",
NotificationImportance.Default);
notificationManager.CreateNotificationChannel(channel);
}
}
Add a ProGuard config file ("proguard.cfg") to your Android project to prevent the SDK linker from killing Google Play and Firebase libraries. Edit the Properties of this file in Visual Studio and set the Build Action to ProguardConfiguration. Even if the option is missing from the dropdown list, Xamarin will recognize it. If you are using d8 and r8 instead of dx and ProGuard in your build, Xamarin will still use this config file and conform to the rules you define within.
# Keep commands are required to prevent the linker from killing dependencies not directly referenced in code
# See: https://forums.xamarin.com/discussion/95107/firebaseinstanceidreceiver-classnotfoundexception-when-receiving-notifications
-dontwarn com.google.android.gms.**
-keep class com.google.android.gms.** { *; }
-keep class com.google.firebase.** { *; }
Hope this helps and if I've missed anything I will update with further details.
The #AndrewH solution worked for me. With one missing detail. The Firebase messaging service will get called also when the app is killed. When the app is killed, only the constructor of the service will get called because internally firebase knows your app is killed. So, before you initialize any code that will handle notifications on foreground or any code that interacts with Xamarin forms, you should check if Xamarin forms has been initialized. For example:
if (Xamarin.Forms.Forms.IsInitialized)
{
// Do stuffs.
}
Otherwise your app will crash when it receives a push notification from killed state.
Also you should know that if the app is killed or Xamarin.Forms.Forms.IsInitialized == false, you should not try to execute any code. Just leave it. Firebase will just show the notification for you. You will just handle the notification when the user click on the notification from the system tray in your MainActivity.OnCreate().
I'm trying to implement push notification in an Android app.
As I understand, we need a sender and receiver installed on the device.
I googled on GCM an found that I need to create a Project in Google dev console.
I got a project ID and an API key.
I found there are third party applications (mixpanel) which can send
push notifications with the information above. Can I use Google Play to
send notifications to all app users?
On the programming side, I got permissions required in the Manifest.xml file
<uses-permission android:name="android.permission.INTERNET" />
<!-- GCM connects to Internet Services. -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- GCM requires a Google account. -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- Keeps the processor from sleeping when a message is received. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Creates a custom permission so only this app can receive its messages. -->
<permission
android:name="com.androidexample.gcm.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.androidexample.gcm.permission.C2D_MESSAGE" />
<!-- This app has permission to register and receive data message. -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- Network State Permissions to detect Internet status -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Permission to vibrate -->
<uses-permission android:name="android.permission.VIBRATE" />
And a broadcast receiver
<receiver
android:name="com.google.android.gcm.GCMBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<!-- Receives the actual messages. -->
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<!-- Receives the registration id. -->
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.androidexample.gcm" />
</intent-filter>
</receiver>
<service android:name="com.androidexample.gcm.GCMIntentService" />
From here on, I got confused about the "sender" and "receiver" parts.
Do I have to implement GCMBroadcastReceiver?
If I want to implement only the Receiving notification part, how does it look like ?
Any reference to a working code example will be good.
Yes, you have to implement GcmBroadcastReceiver class to catch messages from GCM service. The purpose of the receiver is to start the intent service even if the app is off. Here is the most common implementation:
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
The purpose of the intent service is doing something with the caught GCM message:
public class GCMIntentService extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
// Getting GCM message fields
Bundle extras = intent.getExtras();
// Doing something with the message.
// E.g. showing push notification
}
}
You can see e.g. this article for details.
I am trying to set up a Google App engine server endpoint that sends Google Cloud Messages to android devices. So far, the device registers using GoogleCloudMessaging.register() and sends the generated regID to the server. The server then can send a message to the regIDs it has saved. Through the log messages I can see that the regIDs are all arriving correctly, and the server is sending messages correctly. The devices though aren't getting any messages.
This is the receiver in the manifest:
<receiver
android:name="---.app.MessageReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.permission.RECEIVE" />
<category android:name="---.app" />
</intent-filter>
</receiver>
This is the code for the receiver:
public class MessageReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("---", "RECIVED A MESSAGE!!!!!!");
// Explicitly specify that GcmIntentService will handle the intent.
ComponentName comp = new ComponentName(context.getPackageName(),
GCMReceiverService.class.getName());
// Start the service, keeping the device awake while it is launching.
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
The service never gets started, and the log message here is never shown.
Is there something I'm missing?
EDIT - these are the permissions:
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="---.app.permission.C2D_MESSAGE" />
<permission
android:name="---.app.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
There is a working solution on SO Android GCM basic implementation
The author edited his post, so you should just take it as an example. Basically, without correct permissions set AND correct Broadcast's constructor used, android won't give a damn about messages sent to an application.
Please set your permissions and other things accordingly and try again.
There is really nice post later down the comments, way under accepted one.
Really stupid mistake. Instead of .intent.RECIEVE in the intent filter it was .permission.RECEIVE.
I have created a sample project in Google Console. Referring the sample from this site, http://code.google.com/p/blog-samples/downloads/detail?name=gcm_sample_update1.rar&can=2&q=#makechanges I got my registration Id. Now I don't know what to do next, how I can implement push notification in my app. I want to use Urban Airship to send push to my app. I have created a app in Urban Airship, by giving the package name, API key obtained through Google console. To my server I need to send the Apid to get the messages. But i don't know how to get the Apid.
It is explained here: http://developer.android.com/guide/google/gcm/gs.html
Writing the Android Application
You have to download and add gcm libraries.
Then make changes in manifest:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="xx"/>
and
<permission android:name="my_app_package.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="my_app_package.permission.C2D_MESSAGE" />
inserting Your package name in "my_app_package".
Add other permissions
<!-- App receives GCM messages. -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- GCM connects to Google Services. -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- GCM requires a Google account. -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- Keeps the processor from sleeping when a message is received. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
receiver
<receiver android:name="com.google.android.gcm.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="my_app_package" />
</intent-filter>
</receiver>
Again write Your package name in "my_app_package".
Then declare your service
<service android:name=".GCMIntentService" />
You have to create a class named GCMIntentService. This class is going to handle the push notifications.
It should look like this:
class GCMIntentService extends com.google.android.gcm.GCMBaseIntentService{
public void onRegistered(Context context, String regId){}
public void onUnregistered(Context context, String regId){}
public void onError(Context context, String errorId){}
public void onMessage(Context context, Intent intent){}
}
In onMessage() you will have to handle the push message. You can use notifications.
Then in Your starting Activity you will have to put this
GCMRegistrar.checkDevice(this);
GCMRegistrar.checkManifest(this);
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
GCMRegistrar.register(this, SENDER_ID);
} else {
Log.v(TAG, "Already registered");
}
in onCreate().
SENDER_ID is the String containing all numbers that you got from the google console.
Well.. There are 3 steps to get APID
1. Run the application
2. Open logcat
3. Find the APID
Note first time you may get APID(Android Push ID) null so run it again you will get
If you want to use Urbanairship you have to use their SDK for user registration (look here), cause UA create a new id for each device and not using Google registration Id when pushing message using their API.
Not sure why they doing it like that but that it... =\
I am attempting to register my device with C2DM and am having major issues. I have followed several tutorials, all of which are very similar. I believe the issue has to do with the registration intent that it sends to the C2DM server. Does anyone have any suggestions. The following is the relevant code:
Manifest: The permissions (outside my application tag):
<!-- Used for C2DM -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.companyname.parade.permission.C2D_MESSAGE" />
This is the Intent registration (inside my application tag):
<receiver
android:name=".C2DMReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<!-- Receive the actual message -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.companyname.parade" />
</intent-filter>
<!-- Receive the registration id -->
<intent-filter>
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.companyname.parade" />
</intent-filter>
</receiver>
The following is what I call to register my device to the C2DM server (it starts the service that contacts the C2DM servers that is suppose to send back a registration Intent with my registartionID in it). It is located in a file called C2DMessaging:
public static void register(Context context) {
Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
registrationIntent.putExtra(EXTRA_SENDER, SENDER_ID);
ComponentName name = context.startService(registrationIntent);
if(name == null){
// FAIL!
Log.d(TAG, "FAIL");
}else{
// SUCCESS
Log.d(TAG, "Success");
}
}
The ComponentName info is the following:
com.google.android.gsf/com.google.android.gsf.gtalkservice.PushMessagingRegistrar
There is no logcat output. My receiver (named C2DMReceiver) is the following:
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (C2DMessaging.INTENT_REGISTRATION_CALLBACK.equals(action)) {
// Registration Intent
Log.w(TAG, "Registration Receiver called");
handleRegistration(context, intent);
} else if (action.equals(C2DMessaging.INTENT_RECEIVED_MESSAGE_CALLBACK)) {
Log.w(TAG, "Message Receiver called");
handleMessage(context, intent);
} else if (action.equals(C2DMessaging.INTENT_C2DM_RETRY)) {
C2DMessaging.register(context);
}
}
This does not get called at all.
Edit: This whole thing was a stupid mistake on my part. I simply forgot a step somehow in the tutorials I read. I need to add this to my permissions:
<permission android:name="com.companyname.parade.permission.C2D_MESSAGE" android:protectionLevel="signature" />
Thanks to MisterSquonk for the response.
From the Google docs for C2DM for Creating the Manifest, the manifest needs a <permission> entry to complement the <uses-permission> entry for C2D_MESSAGE.
Something like this...
<permission android:name="com.companyname.parade.permission.C2D_MESSAGE" android:protectionLevel="signature" />
This tutorial worked for me in getting me up to speed:
http://www.vogella.com/articles/AndroidCloudToDeviceMessaging/article.html
I'm not sure why you have two intent filters. I only needed one - com.google.android.c2dm.intent.REGISTRATION (see tutorial above for a complete example manifest)
I'd check your manifest is correctly referencing the receiver class - perhaps try a fully-qualified reference to the class. I had an issue at one point where I moved receiver class in my project structure and all messages stopped.
I'd also check that the C2DM account is set up right, and with the right package name.
I'd also try it on another device, as in my experience some Android devices fall off C2DM and just don't receive messages for a period of time. I find sometimes flicking to airplane mode and back sorts it out, but I found testing on several devices essential to rule out problems with a specific device.