I have a simple NotificationListenerService implementation to test the new 4.3 API. The Service itself used to work. After that, I added the sending of a broadcast when a notification of a particular package is added. Now, as soon as I start the service, it throws a DeadObjectException. This is the stack trace:
E/NotificationService﹕ unable to notify listener (posted): android.service.notification.INotificationListener$Stub$Proxy#42c047a0
android.os.DeadObjectException
at android.os.BinderProxy.transact(Native Method)
at android.service.notification.INotificationListener$Stub$Proxy.onNotificationPosted(INotificationListener.java:102)
at com.android.server.NotificationManagerService$NotificationListenerInfo.notifyPostedIfUserMatch(NotificationManagerService.java:241)
at com.android.server.NotificationManagerService$2.run(NotificationManagerService.java:814)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at com.android.server.ServerThread.run(SystemServer.java:1000)
This is how I start the Service
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_start_service:
startService(new Intent(this, ConnectService.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
I can verify that the Service starts, because I do a Log on it's onCreate() and onDestroy().
And here is how the posting of notifications is handled, if it's needed:
#Override
public void onNotificationPosted(StatusBarNotification sbn) {
Log.i(TAG, sbn.getNotification().toString());
if (sbn != null && sbn.getPackageName().equalsIgnoreCase(PKG)) {
Intent intent = new Intent(ConnectService.NOTIFY);
intent.putExtra("notification", sbn.getNotification().toString());
bManager.sendBroadcast(intent);
}
}
The thing that sucks is that the stack trace is of no use. What's going wrong?
Try not starting the service yourself. If you have enabled the NotificationListenerService in the security settings, the system should bind to it automatically.
Alternatively, check your crash logs to see if your service crashed or its process was killed. I believe there is a bug where if your NotificaitonListerService dies, the system will not rebind until you restart your phone or toggle the notifications permission in security settings.
I would like to share my answer due the info I collected from different topics in stackoverflow and my own tests. If your NotificationListenerService fails (exception, like IllegalStateException), the system will kill it and not restore it again. You can see that in the logcat:
592-592/? E/NotificationService﹕ unable to notify listener (posted): android.service.notification.INotificationListener$Stub$Proxy#4291d008
android.os.DeadObjectException
....
If the user goes to Security, Notifications, disable and enable your app, it is still not working. Why? Because it will only by restarted if the user restart the phone or if the user re-enable the option BUT going thru your app using:
startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
So we need to check two things, first if the option is enabled:
private boolean checkNotificationSetting() {
ContentResolver contentResolver = getContentResolver();
String enabledNotificationListeners = Settings.Secure.getString(contentResolver, "enabled_notification_listeners");
String packageName = getPackageName();
return !(enabledNotificationListeners == null || !enabledNotificationListeners.contains(packageName));
}
If it is enabled, we check if the service is Death:
private boolean isNLServiceCrashed() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> runningServiceInfos = manager.getRunningServices(Integer.MAX_VALUE);
if (runningServiceInfos != null) {
for (ActivityManager.RunningServiceInfo service : runningServiceInfos) {
//NotificationListener.class is the name of my class (the one that has to extend from NotificationListenerService)
if (NotificationListener.class.getName().equals(service.service.getClassName())) {
if (service.crashCount > 0) {
// in this situation we know that the notification listener service is not working for the app
return true;
}
return false;
}
}
}
return false;
}
What is this service.crashCount ? Documentation says:
Number of times the service's process has crashed while the service is running.
So if its more than 0, it means it's already death. Therefore, in both cases, we have to warning the user and offer the possibility to restart the service using the intent I posted before:
startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
Of course, if the service crashes, it will be good to detect why and when to prevent it too.
Related
Having an android app which has service running to listen to the FCM notification.
By app in killed state I mean when swipe off the app from the recent activist app list, or close the app by tapping on the home button, or backpress on the app until the app closes (after all activities are popped out from backstack), or for any reason the OS killed the app.
There are functions could be used with the app's packagename to get some app's state info.
this one can help to tell the app is in background, but may not be killed.
public class ArchLifecycleApp extends Application implements LifecycleObserver {
#Override
public void onCreate() {
super.onCreate();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
//App in background
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
// App in foreground
}
}
this one can tell app is in FG only:
boolean isAppInFG(Context appContext, String packageName) {
boolean appInFG = false;
ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses != null) {
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName.equals(packageName)) {
appInFG = true;
break;
}
}
}
return appInFG;
}
after pressed home button, or swipe out the app from the recent application list, the appProcess.importance is always 300 and is same as if the app is in background (covered by other app).
Question: in this case is there way to tell the app is killed (not just simply in background)?
In a comment you wrote:
I want to determine: should the app go through a fresh re-launch (app
is killed) or just bring the app to front?
If you use a "launch Intent", this will handle all of this for you. If the app is already running, it will just bring the app to the foreground in whatever state it was in. If the app is not running, it will launch the app fresh.
To get a "launch Intent", you can use PackageManager.getLaunchIntentForPackage()
Not sure if I understand correctly but according to this link
Deliver silent notifications and wake up your app in the background on the user's device.
It sounded to me that it's possible to perform some action even if the app has been killed.
Currently I'm using OneSignal as below:
OneSignal.addEventListener('received', this.onReceived);
onReceived(store, notification) {
store.dispatch(receivedNotification({ notification }));
}
However the above will only be able to dispatch action if the app is in background or foreground, but once the app been killed, despite receiving notification successfully, onReceived event will not be fired.
So my question is whether is it possible to "wake" my RN app in the background and dispatch a redux action?
There is no event that you can catch before app is killed. You also cannot prevent the user from killing your app. And once it is killed you cannot 'wake' it up, as your code is not running. You can only do such tasks when the app is in the background/foreground.
Yes, it is possible you can execute some event if RN has been killed.
now the question is how basically I am also using react-native and I have got some challenges also, I also want to execute some event if RN has been killed, but I did not get any answer actually my concern is to open an app which can and show a call screen when calling notification is received so I dig a lot and I found a solution,
first I created a bridge connection between javascript to java and then write a wakeful service which gets called every time when we receive notification and then I call my background intent service and in this service I wake up my activity and set some flags which help me to open screen when screen is lock depends on the conditions
// receiver Service //
public class MessagingService extends WakefulBroadcastReceiver {
private static final String TAG = "FirebaseService";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, String.valueOf(!isAppOnForeground(context)));
if (intent.getExtras() != null) {
if (!isAppOnForeground((context))) {
//This get called every time you receive notification
}
}
}
private boolean isAppOnForeground(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
final String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
}
I'm targeting sdk version 27 with a minimum version of 19 and trying to get a service that runs continuously in the background. I tried different service start options but it still got killed with the app. I tried using a BroadcastReceiver to start the service when it got killed but that gave me an error saying that the app was in the background and couldn't start a service so I tried using the JobScheduler and that gave me the same error. How is this supposed to be done? For example, if I were making a pedometer app, how could I keep that running in the background?
In oreo release Android defined limits to background services.
To improve the user experience, Android 8.0 (API level 26) imposes
limitations on what apps can do while running in the background.
Still if app need to run its service always, then we can create foreground service.
Background Service Limitations: While an app is idle, there are limits
to its use of background services. This does not apply to foreground
services, which are more noticeable to the user.
So create a foreground service. In which you will put a notification for user while your service is running. See this answer (There are many others)
Now what if you don't want a notification for your service. A solution is for that.
You can create some periodic task that will start your service, service will do its work and stops itself. By this your app will not be considered battery draining.
You can create periodic task with Alarm Manager, Job Scheduler, Evernote-Jobs or Work Manager.
Instead of telling pros & cons of each one. I just tell you best. Work manager is best solution for periodic tasks. Which was introduced with Android Architecture Component.
Unlike Job-Scheduler(only >21 API) it will work for all versions.
Also it starts work after a Doze-Standby mode.
Make a Android Boot Receiver for scheduling service after device boot.
I created forever running service with Work-Manager, that is working perfectly.
Since Android 8.0 many background service limitations have been introduced.
Two solutions:
if you need to get total control of task and execution timing, you have to choose Foreground Service.
Pros: your app will be considered to be alive, then is more unlikely that the os will kill it to free resources.
Cons: your user will always see the Foreground Notification.
if you need to schedule periodically task, then Work Manager (introduced in Google I/O 18) is the best solution. This component choose the best possible scheduler (Jobscheduler, JobDispatcher, AlarmManager..). Keep in mind that work manager APIs are useful only for the tasks that require guaranteed execution and they are deferrable.
Ref: Android Dev Documentation
The only solution I would suggest is using Firebase Cloud Messages.
Or foreground services.
Using BroadcastReciever we can run backgrouund service continuously, but if it will get killed , destroy automatically re-instance the old service instance
When service stops forcefully it will call onDestroy() method, in this case use one receiver and send one broadcast when ever service destroy and restart service again. in thee following method com.android.app is custom action of reciever class which extends BroadcastReciever
public void onDestroy() {
try {
myTimer.cancel();
timerTask.cancel();
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent("com.android.app");
intent.putExtra("valueone", "tostoreagain");
sendBroadcast(intent);
}
and in onReceive Method
#Override
public void onReceive(Context context, Intent intent) {
Log.i("Service Stoped", "call service again");
context.startService(new Intent(context, ServiceCheckWork.class));
}
In case device is restarted then we have onBootCompleted action for receiver to catch
When you are targeting SdkVersion "O"
In MainActivity.java define getPendingIntent()
private PendingIntent getPendingIntent() {
Intent intent = new Intent(this, YourBroadcastReceiver.class);
intent.setAction(YourBroadcastReceiver.ACTION_PROCESS_UPDATES);
return PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
here we use PendingIntent with BroadcastReceiver and This BroadcastReceiver has already been defined in AndroidManifest.xml.
Now in YourBroadcastReceiver.java class which contains an onReceive() method:
Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_PROCESS_UPDATES.equals(action)) {
NotificationResult result = NotificationResult.extractResult(intent);
if (result != null) {
List<Notification> notifications = result.getNotification();
NotificationResultHelper notificationResultHelper = new
NotificationResultHelper(
context, notifications);
// Save the notification data to SharedPreferences.
notificationResultHelper.saveResults();
// Show notification with the notification data.
notificationResultHelper.showNotification();
Log.i(TAG,
NotificationResultHelper.getSavedNotificationResult(context));
}
}
}
}
as you say:
I tried using a BroadcastReceiver to start the service when it got
killed but that gave me an error saying that the app was in the
background and couldn't start a service
in Oreo when you are in background and you want to start a service that service must be a foreground service use this code:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
if you use this code in Oreo you have a few seconds in onStartCommand to start foreground otherwise your service considered as not responding and may be force close by user (in Android 8 or above)
There is no need to use BroadcastReceiver to start service after it is closed it is enough to just return START_STICKY or START_REDELIVER_INTENT from onStartCommand of your service to restart service after it is closed
A working hack for this is to simply start a foreground service which is only visible for the fraction of a second and starts your background service. In the background service you'd then periodically start the foreground service.
Before I give an example you should really ask yourself if this is the way to go for you, there might be other solutions to given problems (like using JobIntentService etc.); and keep in mind that this is a hack, it might be patched some time around and I'd generally not use it (I tested it with screen off and battery saving enabled though and it stayed alive the whole time - but this might prevent your device from dozing.. again, this is a dirty hack!)
Example:
public class TemporaryForegroundService extends Service {
public static final int NOTIFICATION_ID = 666;
private static Notification notification;
#Override
public void onCreate() {
super.onCreate();
if(notification == null)
notification = new NotificationCompat.Builder(this, NotificationChannels.importantChannel(this)).
setSmallIcon(R.mipmap.ic_launcher).setContentTitle("The unseen blade").setContentText("If you see me, congrats to you.").build();
startForeground(NOTIFICATION_ID, notification);
startService(new Intent(this, PermanentBackgroundService.class));
stopForeground(true);
stopSelf();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
public class PermanentBackgroundService extends Service {
private Runnable keepAliveRunnable = new Runnable() {
#Override
public void run() {
keepServiceAlive();
if(handler != null) handler.postDelayed(this, 15*1000);
}
};
private Handler handler;
public void onCreate(){
handler = new Handler();
handler.postDelayed(keepAliveRunnable, 30* 1000);
}
public void onDestroy() {
super.onDestroy();
keepServiceAlive();
}
private void keepServiceAlive() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(new Intent(PermanentBackgroundService.this, TemporaryForegroundService .class));
} else {
startService(new Intent(PermanentBackgroundService.this, TemporaryForegroundService .class));
}
}
}
I am working in application that needs make a synchronization every night. I use Alarm Manager that calls a BroadcastReceiver at the hour that I want. The problem is that I cant make a synchronization if the application is running in foreground to avoid losing data. So I need to know in Broadcast Receiver if the app is running in foreground to cancel this synchronization.
I tried solutions that I found in StackOverflow:
Checking if an Android application is running in the background
But this parameter is always false in BroadcastReceiver, but true in activites.
Can anyone tell me which is the problem? What am I doing bad?
Really thanks!
Try this way hope this works for you
public class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (isAppForground(context)) {
// App is in Foreground
} else {
// App is in Background
}
}
public boolean isAppForground(Context mContext) {
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasks = am.getRunningTasks(1);
if (!tasks.isEmpty()) {
ComponentName topActivity = tasks.get(0).topActivity;
if (!topActivity.getPackageName().equals(mContext.getPackageName())) {
return false;
}
}
return true;
}
}
Add this permission
<uses-permission android:name="android.permission.GET_TASKS" />
What do you mean by "the application is running in foreground"?
If you mean there is an Activity currently displayed on the screen, then the easiest way would be to make a base Activity class that sets a global boolean in your `Application' class.
Custom Application class:
public class MyApp extends Application
{
public boolean isInForeground = false;
}
Custom base Activity class:
abstract public class ABaseActivity extends Activity
{
#Override
protected void onResume()
{
super.onResume();
((MyApp)getApplication()).isInForeground = true;
}
#Override
protected void onPause()
{
super.onPause();
((MyApp)getApplication()).isInForeground = false;
}
}
I assume you are not synchronising from your BroadcastReceiver - you should instead be launching a Service to do the synchronisation. Otherwise the system might kill your app - you must not be doing any long-running tasks in a BroadcastReceiver.
So before you launch your sync service, check the application boolean to see if your app is "in foreground". Alternatively, move the check inside the sync service, which has the advantage of making the BroadcastReceiver even simpler (I am always in favour of trying to make the receivers have as little logic as possible).
This method has the advantages that it is simple to use, understand, and requires no extra permissions.
in case you don't want to do anything if app in foreground you could simply turn off the receiver on your activity onStart method:
ComponentName receiver = new ComponentName(context, MyReceiver.class);
context.getPackageManager().setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
and you could turn it on onStop method:
ComponentName receiver = new ComponentName(context, MyReceiver.class);
context.getPackageManager().setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
and if receiver is turned off, no alarms will come to it, and your code will not be executed
That method tells you whether any of your activities in your app are currently in the foreground. If you check your MyApplication.isActivityVisible() method from the broadcast receiver, then that should work fine. If its returning false, then maybes no activities are showing.
I'm developing a service that need to run foreground, and users can toggle it on/off through an activity. So basically, the activity MAY be killed, but the service is safe as long as it is not stopped by user.
However, I'm getting this trouble: how to turn off the service if it is on? That mean, if my activity was killed, then restarted, so I get no reference of the started service intent to call stopService.
Below is my current code. It works fine if the user call deactivate after the service is started by the same activity. The button status is always correct, but when my activity is restarted by the OS, this.serviceIntent is null.
protected boolean isServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (SynchronizationService.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
#Override
protected void onResume() {
super.onResume();
this.togglingByCode = true;
this.butActivate.setChecked(this.isServiceRunning());
this.togglingByCode = false;
}
public void onActivateButtonClick(final boolean pIsChecked) {
if (this.togglingByCode) { return; }
if (pIsChecked) {
this.saveSettings();
this.serviceIntent = new Intent(this, SynchronizationService.class);
this.serviceIntent.putExtra(KEY_WEBSERVICE, this.txtWebService.getText().toString());
this.serviceIntent.putExtra(KEY_PASSWORD, this.txtPassword.getText().toString());
this.serviceIntent.putExtra(KEY_INTERVAL, Integer.parseInt(this.txtRefreshInterval.getText().toString()));
this.serviceIntent.putExtra(KEY_TIMEOUT, this.preferences.getInt(KEY_TIMEOUT, DEFAULT_TIMEOUT));
this.serviceIntent.putExtra(KEY_RETRY, this.preferences.getInt(KEY_RETRY, DEFAULT_RETRY));
this.startService(this.serviceIntent);
} else {
this.stopService(this.serviceIntent);
this.serviceIntent = null;
}
}
Please tell me how to stop the service correctly. Thank you.
P.s: I know a trick that make serviceIntent static, but I don't feel safe about it. If there is no any other way, then I will use it.
Simply initialize your serviceIntent in the activity's onCreate... You can start your service, kill your activity, reopen it and stop the service with the same serviceIntent.