FCM messages unable to be handled when app is killed - android

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().

Related

Broadcast Receiver Not Working After Device Reboot in Android

I have already checked all the related questions and have not found any solution for this problem. So this is an absolutely new problem for me.
What I Have
I have an Android app which registers a few broadcast receivers in its manifest. This is what my manifest looks like.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.app.myapp">
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<application
android:name=".base.MyApp"
android:allowBackup="false"
android:icon="#drawable/ic_launcher"
android:label="#string/label_app_name"
android:largeHeap="true"
android:supportsRtl="true"
android:theme="#style/AppTheme"
tools:replace="label, allowBackup">
<receiver android:name=".mics.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<receiver android:name=".PhoneCallReceiver">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
<receiver
android:name=".mics.DeviceAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
<meta-data
android:name="android.app.device_admin"
android:resource="#xml/device_admin" />
</receiver>
<receiver
android:name="com.clevertap.android.sdk.InstallReferrerBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
<meta-data
android:name="com.app.myapp.utils.ImageLoaderModule"
android:value="GlideModule" />
<meta-data
android:name="com.app.myapp.utils.AudioCoverLoaderModule"
android:value="GlideModule" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
<activity
android:name=".core.activities.SplashActivity"
android:excludeFromRecents="true"
android:label="#string/label_app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity-alias
android:name=".core.activities.SplashActivity-Alias"
android:icon="#drawable/ic_launcher"
android:label="#string/label_app_name"
android:noHistory="true"
android:targetActivity="com.app.myapp.core.activities.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY" />
</intent-filter>
</activity-alias>
<activity
android:name=".core.flow.authFlow.activities.AuthFlowActivity"
android:excludeFromRecents="true"
android:label="#string/label_app_name"
android:screenOrientation="portrait" />
<service android:name=".features.fileCloudSync.KillNotificationService" />
</application>
</manifest>
There are 10-15 other activities as well but have been removed for simplicity. And this is the basic boot receiver class. I start a service from here.
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
AlertUtils.showToast(context, "BOOT COMPLETED", Toast.LENGTH_LONG);
}
}
}
and the phone call receiver class looks something like this (it has been simplified as well),
public class PhoneCallReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
AlertUtils.showToast(context, "PHONE CALL RECEIVED", Toast.LENGTH_LONG);
// Simplified for brevity
}
}
}
The Problem
All these receivers work fine when I install the app and start it once. But after I reboot my device these receivers don't work at all. Neither the BootCompleteReceiver nor the PhoneCallReceiver gets their onReceive() method called.
My assumption was that these receivers would get registered automatically after reboot, but it just doesn't work. I need the BootCompleteReceiver to work so that I can start an important service in my app.
My Observations
I have tested this thoroughly. After rebooting the device, the receivers work fine in my Nexus 5X (Nougat), Nexus 6P (Nougat), YU Yuphoria (Lollipop) but not in my OnePlus 3 (Nougat) and Mi 4i (Lollipop).
How can the same code work perfectly on a few devices and not work at all on the other devices? I haven't changed anything at all.
What am I doing wrong here? My app is heavily dependent on these broadcasts and starts services based on these. Any help will be highly appreciated.
EDIT 1
To understand the problem better, I just created a very small test project with just a single activity and the exact same BootCompleteReceiver and PhoneCallReceiver.
But weirdly, this project works perfectly on my OnePlus 3 where my actual app's receivers don't work after a reboot. I was initially assuming that the problem is in the OS or the device somehow, but it is not.
So where is the actual problem? Is it in my app (but it works perfectly on other devices) or in the OS and device (the small test project works fine on the same OS and same device)?
It is really confusing to me. I would need some expert help on this.
EDIT 2
I have tried the suggestion given by #shadygoneinsane. Here are my observations.
1) I tried to send the BOOT_COMPLETED broadcast via ADB.
./adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -p com.app.myapp
And I got this stack trace,
Broadcasting: Intent { act=android.intent.action.BOOT_COMPLETED pkg=com.app.myapp }
java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.intent.action.BOOT_COMPLETED from pid=25378, uid=2000
at android.os.Parcel.readException(Parcel.java:1683)
at android.os.Parcel.readException(Parcel.java:1636)
at android.app.ActivityManagerProxy.broadcastIntent(ActivityManagerNative.java:3696)
at com.android.commands.am.Am.sendBroadcast(Am.java:778)
at com.android.commands.am.Am.onRun(Am.java:404)
at com.android.internal.os.BaseCommand.run(BaseCommand.java:51)
at com.android.commands.am.Am.main(Am.java:121)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:276)
Maybe because my device is not rooted. I am unable to send this broadcast in any way.
2) I tried with the PROCESS_OUTGOING_CALLS broadcast after that.
./adb shell am broadcast -a android.intent.action.PROCESS_OUTGOING_CALLS -p com.app.myapp
I got this,
Broadcasting: Intent { act=android.intent.action.PROCESS_OUTGOING_CALLS pkg=com.app.myapp }
Broadcast completed: result=0
It seems that the broadcast was successful, but I do not see any Toast or any log. I then opened my dialer to dial a number and I can then see the Toast and the log both.
So it seems that sending the broadcast via ADB didn't work, but actually opening the dialer and dialing a number did.
EDIT 3
As per the suggestion from #ChaitanyaAtkuri, I have also tried adding priority to the intent-filters but that didn't work as well.
I have used priorities like 500, 999 and even the highest integer value, but nothing works. This problem is also occurring in some of my friends apps as well. They work in some devices and doesn't work in others.
EDIT 4
I have finally found out the root cause of the problem happening in my OnePlus 3. My OnePlus 3 recently got updated to Nougat and they introduced a feature similar to Mi devices which prevent certain apps from auto-starting after reboot.
Upon disabling this feature my app started receiving broadcasts after reboot perfectly. But this still doesn't explain two things.
1) My small test project is whitelisted automatically in the list of AutoLaunch apps and that is why it works as expected. But how is this possible? Why the OS considers this small app worthy to be auto-started?
2) There are some apps like LockDown Pro, 500 Firepaper which is blacklisted in the AutoLaunch apps screen but still, it receives broadcasts after reboot in my OnePlus 3 and Mi 4i. How is that possible now? Is it somehow possible to programmatically allow my app to auto launch in these devices (OnePlus and Mi)?
EDIT 5
I have tried the solution proposed by #Rahul Chowdhury and it really seems to work very well. After adding the accessibility service the problem is re-solved.
But if the user revokes the accessibility permission after granting it then is there a way for me to programmatically check if the accessibility permission is available to my app?
Here's a tested and working solution on both the devices that you mentioned, OnePlus and Mi.
As you said the auto-start prevention feature on OnePlus and Mi devices prevent apps from starting up their services automatically on boot complete so as to improve the overall device boot speed and battery performance. However, there's a workaround to get your app working even when this feature is turned on.
I have noticed that if you have an AccessibilityService in your app and it is turned on by the user, then your app passes the filter that these manufacturers apply and the app receives it's boot complete event and any other BroadcastReceiver works as expected.
The possible explanation of this trick can be that since AccessibilityService is a system level service, so by registering your own service you are passing the certain filter applied by these manufacturers and as soon as your custom AccessibilityService gets triggered by the OS, your app becomes active in receiving the eligible BroadcastReceiver that you had registered.
So, here's how to do it,
Start by adding this permission to your AndroidManifest.xml,
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>
This will allow you to register your app's AccessibilityService with the system.
Now, add a very basic configuration for your AccessibilityService by creating a file for example my_accessibility_service.xml inside XML folder under your res folder in your project.
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFeedbackType="feedbackSpoken"
android:description="#string/service_desc"
android:notificationTimeout="100"/>
There's just one more step left to do, define your custom AccessibilityService in your project,
public class MyAccessibilityService extends AccessibilityService {
#Override
public void onAccessibilityEvent(AccessibilityEvent event) { }
#Override
public void onInterrupt() {
}
}
Note, since you're not needing the AccessibilityService for any purpose rather than this workaround, you can leave the overridden methods empty.
Finally, just declare your AccessibilityService in your AndroidManifest.xml,
<service
android:name=".MyAccessibilityService"
android:label="#string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/my_accessibility_service"/>
</service>
That's all. Now within your app, just ask your users to turn on the accessibility service for your app from the settings and leave it on and voila! Your app works fine on all devices even where the OS puts a filter on which apps should auto-start on boot.
EDIT 1
Here's how you can check if accessibility service is turned ON or not for your app,
private static final int ACCESSIBILITY_ENABLED = 1;
public static boolean isAccessibilitySettingsOn(Context context) {
int accessibilityEnabled = 0;
final String service = context.getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
context.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
Log.e("AU", "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == ACCESSIBILITY_ENABLED) {
String settingValue = Settings.Secure.getString(
context.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
}
Hope this helps.
Hi I am late to the party but I was following this question from it's start. I know that One-plus and some other OEMs maintain a list of apps which can receive BOOT_COMPLETED broadcast. If your app is not white listed then your app won't be started on boot. Now I've a solution which is very efficient in terms of memory and resources and guaranteed to start your task or service after reboot or hard boot also does not need AccessibilityService as proposed in this answer. Here it goes..
Add the follwing permission in your manifest file
2.If you don't have a dependency on com.google.android.gms:play-services-gcm, add the following to your build.gradle's dependencies section:
compile 'com.firebase:firebase-jobdispatcher:0.5.2'
Otherwise add the following:
compile 'com.firebase:firebase-jobdispatcher-with-gcm-dep:0.5.2'
This is a library from firebase team which depends on google-play-service library to schedule your jobs and from my point of view google-play-service has the permission to start at boot so instead of system ,google-play-service will run your job as soon as device rebooted.
Now this step is easy Just define a JobService class
public class MyJobService extends JobService {
#Override
public boolean onStartJob(JobParameters job) {
Log.v("Running", "====>>>>MyJobService");
return false; // Answers the question: "Is there still work going on?"
}
#Override
public boolean onStopJob(JobParameters job) {
Log.v("Stopping", "====>>>>MyJobService");
return true; // Answers the question: "Should this job be retried?"
}
}
Add your Job Service in manifest file.
Schedule this job anywhere you want for e.g when your app start.
FirebaseJobDispatcher dispatcher =
new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
Bundle myExtrasBundle = new Bundle();
myExtrasBundle.putString("some_key", "some_value");
Job myJob = dispatcher.newJobBuilder()
// the JobService that will be called
.setService(MyJobService.class)
// uniquely identifies the job
.setTag("my-unique-tag-test")
// repeat the job
.setRecurring(true)
// persist past a device reboot
.setLifetime(Lifetime.FOREVER)
// start between 0 and 60 seconds from now
.setTrigger(Trigger.executionWindow(0, 60))
// don't overwrite an existing job with the same tag
.setReplaceCurrent(false)
// retry with exponential backoff
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
// constraints that need to be satisfied for the job to run
.setExtras(myExtrasBundle)
.build();
dispatcher.mustSchedule(myJob);
6.That's it!! Now you can execute your task or service on device boot no matter you are in white list or not.
There is one point to note that Google Play Service must be installed on device otherwise it won't work.
#Aritra, Try this
<receiver
android:name=".mics.BootReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter android:priority="500" >
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Remove quickBoot intent filter and try running it, as per the documentation we only required BootCompleted to acheive it. May be it is interrupting this.
Also one more important point to Note :
Don't rely completely or test on Mi devices as they have their own OS which halts the basic features of Android, like they stop the Push notifications services and background services just to optimize battery usage. To test this on Mi device, mark your app as "AutoStart" in Security app and then try.
You can ask user for autostart permission, and direct them to the required settings page:
private void autoStart() {
try {
Intent intent = new Intent();
String manufacturer = android.os.Build.MANUFACTURER;
if ("xiaomi".equalsIgnoreCase(manufacturer)) {
intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"));
} else if ("oppo".equalsIgnoreCase(manufacturer)) {
intent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
} else if ("vivo".equalsIgnoreCase(manufacturer)) {
intent.setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity"));
} else if ("Letv".equalsIgnoreCase(manufacturer)) {
intent.setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
} else if ("Honor".equalsIgnoreCase(manufacturer)) {
intent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
} else if ("oneplus".equalsIgnoreCase(manufacturer)) {
intent.setComponent(new ComponentName("com.oneplus.security", "com.oneplus.security.chainlaunch.view.ChainLaunchAppListAct‌​ivity"));
}
List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() > 0) {
startActivity(intent);
}
} catch (Exception e) {
Log.e("exc", String.valueOf(e));
}
}
After doing this, receiver always got triggered on reboot.
The way IntentFilters work is that each <intent-filter></intent-filter> contains one way of firing up the component. If you have multiple ways of firing it up (like two actions that you want to listen to in one BroadcastReceiver), you'll need an independent <intent-filter></intent-filter> definition for each.
Hence, you can try changing:
<receiver android:name=".mics.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
to:
<receiver android:name=".mics.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
Read more here: Intents and Intent Filters | Android Developers
EDIT
If it still doesn't work, you can try to test if your manifest declaration is done correctly. Try executing the following command in your terminal, keeping the test device connected to the computer:
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n com.app.myapp/.mics.BootReceiver
If this doesn't work, you should recheck the relative package declaration of the receiver in your manifest file.
EDIT 2
It may sound weird but try following these steps:
Uninstall the app from your phone (ensure it is uninstalled for all users)
Reboot your phone
Clean the project
Build and run the project in your device again
I have been struggling with this issue from almost a year. In all my apps, I show a notice to users to disable battery optimization for my app.
After a lot of testing on One Plus devices, I am able to receive boot completed broadcast when battery optimization is turned off for my app. In my opinion, it is much better than the accessibility service discussed above.
The simplest way to ask the user to disable the battery optimization for your app is to show some kind of notice, and open the battery optimization page when the user clicks on it. You can use the below code to do that.
public void openPowerSettings(View v) {
/* Make Sure to add below code to manifest
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
*/
try {
Intent i = new Intent(android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivityForResult(i, 1);
} catch (Exception e) {
Log.e (TAG, "Exception: " + e.toString());
}
}
And you can also hide the notice if below function returns true.
public static boolean is_ignoring_battery_optimizations(Context context) {
String PACKAGE_NAME = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(context.POWER_SERVICE);
boolean status = true;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
status = pm.isIgnoringBatteryOptimizations(PACKAGE_NAME);
}
return status;
}
How to start service on device boot(autorun app, etc.)
For first: since version Android 3.1+ you don't receive BOOT_COMPLETE if user never started your app at least once or user "force closed" application.
This was done to prevent malware automatically register service. This security hole was closed in newer versions of Android.
Solution:
Create app with activity. When user run it once app can receive BOOT_COMPLETE broadcast message.
For second: BOOT_COMPLETE is sent before external storage is mounted. If app is installed to external storage it won't receive BOOT_COMPLETE broadcast message.
In this case there is two solution:
Install your app to internal storage
Install another small app in internal storage. This app receives BOOT_COMPLETE and run second app on external storage.
If your app already installed in internal storage then code below can help you understand how to start service on device boot.
In Manifest.xml
Permission:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Register your BOOT_COMPLETED receiver:
<receiver android:name="org.yourapp.OnBoot">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
Register your service:
<service android:name="org.yourapp.YourCoolService" />
In receiver OnBoot.java:
public class OnBoot extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
// Create Intent
Intent serviceIntent = new Intent(context, YourCoolService.class);
// Start service
context.startService(serviceIntent);
}
}
For HTC you maybe need also add in Manifest this code if device don't catch RECEIVE_BOOT_COMPLETED:
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
Receiver now look like this:
<receiver android:name="org.yourapp.OnBoot">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
How to test BOOT_COMPLETED without restart emulator or real device?
It's easy. Try this:
adb -s device-or-emulator-id shell am broadcast -a android.intent.action.BOOT_COMPLETED
How to get device id? Get list of connected devices with id's:
adb devices
adb in ADT by default you can find in:
adt-installation-dir/sdk/platform-tools
Enjoy! )

Push notifications behaviour while app running in background (Parse/Unity/Android)

I've been trying Parse's integration tutorials and examples, and I can't figure out how to make the push notifications show in tray when the app is running in background.
Notifications do appear when the app is closed, though!
Also, when in foreground, push notification events do trigger correctly.
Summing up, push notifications work perfectly except when running in background.
The notification shows in the log like this:
07-02 04:43:06.979: I/GCM(22111): GCM message com.parse.parseunitypushsample 0:1435804985959681%368c544ef9fd7ecd
07-02 04:43:07.033: W/GCM-DMM(22111): broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg=com.parse.parseunitypushsample (has extras) }
07-02 04:43:07.041: I/ParsePushService(16145): Push notification received. Payload: {"alert":"A test push from Parse!","push_hash":"2bf06f5e2a92eab2fcb855fc1117fa33"}
07-02 04:43:07.041: I/ParsePushService(16145): Push notification is handled while the app is foregrounded.
Notice it says "foregrounded", even when some other app is in foreground. This bothers me a bit, as if Parse decided not to show the notification because it concludes that it's on foreground. Just my guess.
I'm using:
Unity 5.1.1f1 (free)
Android 5.1.1
Parse and tutorial project provided here: https://parse.com/apps/quickstart#parse_push/unity/android/new (with only modifications needed to make it run in Unity 5.x)
Provided manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.parse.parseunitypushsample" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:protectionLevel="signature" android:name="com.parse.parseunitypushsample.permission.C2D_MESSAGE" />
<uses-permission android:name="com.parse.parseunitypushsample.permission.C2D_MESSAGE" />
<application android:label="#string/app_name" android:icon="#drawable/app_icon">
<!-- Added "com.unity3d.player" below to avoid crash -->
<activity android:name="com.unity3d.player.UnityPlayerActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="com.parse.ParsePushBroadcastReceiver" 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="com.parse.parseunitypushsample" />
</intent-filter>
</receiver>
<service android:name="com.parse.ParsePushService" />
</application>
</manifest>
I'm just building the tutorial project, but it needs Unity upgrade, and a couple small fixes to run. Otherwise, it's clean and seems to correctly receive push notifications, but not showing them while running on background.
Is that the expected behaviour, or am I doing something wrong?
Thank you :)
If you got that, then you are almost there! I think you need to add this script to one of your GameObjects:
using UnityEngine;
using System.Collections;
using Parse;
public class ParsePushRegistration : MonoBehaviour {
// Use this for initialization
void Start () {
#if UNITY_ANDROID
ParsePush.ParsePushNotificationReceived += (sender, args) => {
AndroidJavaClass parseUnityHelper = new AndroidJavaClass ("com.parse.ParseUnityHelper");
AndroidJavaClass unityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject> ("com.unity3d.player.UnityPlayerNativeActivity");
// Call default behavior.
parseUnityHelper.CallStatic ("handleParsePushNotificationReceived", currentActivity, args.StringPayload);
};
#endif
}
}
Check and keep track of this question, it might be usefull to you:
Parse Unity Push Sample not working
Not sure if this helps, but I was able to work around this by editing the plugin source available here. To do this I simply cached the string value of the push msg in the ParsePushUnityHelper.java class and then retrieved it from Unity c# via Android CallStatic method. You can then use one of the publicly available Json modules to convert this from a json string to a dictionary. Keep in mind you'll have to recompile and link the plugin .jar to your Unity project. Good luck!

Location Based Push Notifications For Android

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);
}
}

My WearableListenerService is not started when Android wear restarts

I have an android Application that (among other things) show notifications on an Android Wear device.
This is done by having a class in the Wear module extend WearableListenerService. In the Wear module, I also have a class that extends BroadcastReceiver.
The scenario here is:
- Run the application from Android Studio
- Use the phone application so that a Notification is shown on the Wear device
- Restart the Wear device
Now what I want is that if I make the phone show another notification, it should appear on the wearable. So is not the case, because the WearableListenerService is not started...
So I let the BroadcastManager listen to ACTION_BOOT_COMPLETED events:
#Override
public void onReceive(Context context, final Intent intent) {
Log.d(TAG, "onReceive!");
if(intent==null)
return;
if(intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Log.d(TAG, "Action boot completed");
ComponentName c = context.startService(new Intent(context, OngoingNotificationListenerService.class));
Log.d(TAG, "returned " + c);
return;
}
.
.
.
And in my Manifest file:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
.
.
.
<receiver android:name=".ActionsReceiver" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="com.XX.receiver.action_pause" />
<action android:name="com.XX.receiver.action_resume" />
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
Now the problem is that my application doesn't receive the BOOT_COMPLETED action. I have checked the log and seen that the other listeners on the watch receives this event after a reboot, but not mine.
I have seen several posts about this, for example
Android BOOT_COMPLETED not received when application is closed
I think this might be quite similar to my problem.
My android wear app doesnt have a "mainactivity" that a user can start - it's ONLY the listener service and receiver. Although, I quickly implemented a main activty with a launcher intent, so that I could launch the app from the launcher on the watch. This didn't affect the situtation at all. Once the watch is restarted, it wont show a notification from my app until I reinstall it from android studio.
So have I missed something important? Is my WearableListenerService supposed to start itself without my interaction when the watch is restarted? (It doesn't...) Or does it have anything to do with that this is a developer version of the app?
(Note: I have also tried shut down and then start - no difference)
Try adding <category android:name="android.intent.category.DEFAULT" /> as a child of you <intent-filter>
You need to add your service to your wear module's manifest and add the BIND_LISTENER DATA_CHANGED action to it:
To listen to data items and messages on a path of /prefix, the new syntax looks like this (according to the android tools site):
<service android:name=".ExampleWearableListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data android:scheme="wear" android:host="*" android:pathPrefix="/prefix" />
</intent-filter>
</service>

Not receiving GCM on device

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.

Categories

Resources