I have developed small app to detect changes in network like on, off or connection change Wifi to Ethernet, whenever app closed or running in all cases.
Provided code working for me upto Nuget 7, when testing app in Oreo 8 background services not working when app terminated.
How can I get it work in Oreo?
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
Intent vpnServiceIntent = new Intent(getBaseContext(), MyService.class);
startForegroundService(vpnServiceIntent);}}
WifiReceiver.java file
public class WifiReceiver extends BroadcastReceiver {
static final String CONNECTIVITY_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (CONNECTIVITY_CHANGE_ACTION.equals(action)) {
if (!isConnected()) {
if (context != null) {
Toast.makeText(context," Not connected...",Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(context,"connected...",Toast.LENGTH_LONG).show();
}
}
}
}
MySevice.java file
public class MyService extends Service
{
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
IntentFilter filter = new IntentFilter();
filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
WifiReceiver receiver =new WifiReceiver();
registerReceiver(receiver,filter);
return START_STICKY;
}
}
Manifest.xml file
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
<receiver android:name=".WifiReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
<service android:name=".MyService" />
</application>
Note: If I call startForeground(101, notification); in onCreate of MyService class, my above code is working but showing permanent notification icon on top most status bar, that I don't want at all.
since Android 8.0 (API level 26) it is basically impossible to run background service while app is not visible because of Battery optimizations and security reasons.
It sucks, many useful apps can not run and work normally.
They recommend to use ForegroundService which requires to show notification.
It would be almost okay, but these ForegroundServices also gets killed after some time.
To avoid killing them you need to make BatteryOptimization prompt so user would let service running in background without killing.
But it is not over yet... Services is still being killed on most of Manufactures like Samsung, Huawei and so on because they has they own badly implemented BatteryOptimizations running parallel with native one... and if user want some app avoid to be killed while running in background it has to go long way to settings find provider specific settings and let app run....
here is an example how to change these provider specific settings on Slack
I think it is worst thing that happened to Android.....
Related
I have the following service declared in the manifest:
<application
...
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.example.android.test.TestService"
android:process=":Remote"
android:permission="android.permission.WAKE_LOCK"/>
<receiver android:name="com.example.android.test.TestService">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
</intent-filter>
</receiver>
</application>
and this is the Service class
public class TestService extends Service implements SensorEventListener {
public class BatteryReceiver_andFileChecker extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//TODO
}
// constructor
public BatteryReceiver_andFileChecker(){
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
//TODO
return START_STICKY;
}
#SuppressLint("WakelockTimeout")
#Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "Logging service started new", Toast.LENGTH_SHORT).show();
//Acquire wake lock
PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WLTAG:MyWakelockTag");
wakeLock.acquire();
//Display notification
this.notIntent = new Intent(this, MainActivity.class);
this.pendingIntent = PendingIntent.getActivity(this, 0, this.notIntent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, this.channelID).setSmallIcon(R.drawable.ic_launcher_background).setContentTitle("Test").setContentText("Sensor is recording").setPriority(NotificationCompat.PRIORITY_DEFAULT).setContentIntent(this.pendingIntent);
startForeground(this.NOTIFICATION, builder.build());
//BatteryCheck
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
mReceiver = new BatteryReceiver_andFileChecker();
registerReceiver(mReceiver, filter);
}
#Override
public void onDestroy() {
super.onDestroy();
//cancel notification
stopForeground(true);
//Unregister battery receiver
unregisterReceiver(mReceiver);
//release wakeLock
wakeLock.release();
//Stop Service
stopSelf();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
// super.onBind(intent);
return null;
}
#Override
public void onSensorChanged(SensorEvent event) {
//TODO
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
And this services is created and terminated on two different "onclick" functions from the MainActivity
public class MainActivity extends AppCompatActivity {
...
public void onClickStart(View v) {
// Start Service
this.intent = new Intent(getApplicationContext(), TestService.class);
this.intent.putExtra("foo",foo);
startService(this.intent);
}
public void onClickStopAcquisition(View v) {
// Stop Service
stopService(this.intent);
}
}
This code runs as expecten in Android 6.0.1 and does not work on Android 10...
When I debug de app, no errors are fired when pressed the Start and Stop buttons in both OS, nevertheless, android 6.0.1 fires the service and android 10 does not...
Any reason why?
Just to keep in mind when understanding my implementation, my intention is to make a service which keeps running and doing stuff even if the user is not active in the application. Since I implementen the SensorEventListener to record data from sensors, my intention is to record data while the user might be interacting with the phone or even doing nothing (after he presses the power button of the phone, the service keeps running acquiring data and performing actions)
Then, the service should be terminated either when the user clicks the stop button or either when the MainActivity is terminated.
Thank you!
i guess you should read more about background services and broadcast in android API 26 or higher
From the official documentation available here
If an app registers to receive broadcasts, the app's receiver consumes resources every time the broadcast is sent. This can cause problems if too many apps register to receive broadcasts based on system events; a system event that triggers a broadcast can cause all of those apps to consume resources in rapid succession, impairing the user experience. To mitigate this problem, Android 7.0 (API level 24) placed limitations on broadcasts, as described in Background Optimization. Android 8.0 (API level 26) makes these limitations more stringent.
Apps that target Android 8.0 or higher can no longer register
broadcast receivers for implicit broadcasts in their manifest. An
implicit broadcast is a broadcast that does not target that app
specifically. For example, ACTION_PACKAGE_REPLACED is an implicit
broadcast, since it is sent to all registered listeners, letting them
know that some package on the device was replaced. However,
ACTION_MY_PACKAGE_REPLACED is not an implicit broadcast, since it is
sent only to the app whose package was replaced, no matter how many
other apps have registered listeners for that broadcast. Apps can
continue to register for explicit broadcasts in their manifests. Apps
can use Context.registerReceiver() at runtime to register a receiver
for any broadcast, whether implicit or explicit.
Broadcasts that require a signature permission are exempted from this restriction, since these broadcasts are only sent to apps that are signed with the same certificate, not to all the apps on the device
you should work with JobScheduler
Important Update
to answer your question in comment : use WorkManager for deferrable background tasks.
This library is backward compatible
It use JobScheduler,FirebaseJobDispatcher or AlarmManager
No Need to depend on play service library.
Recommended by Google for deferrable background work.
Can use features like chaining, constraints etc.
I have a BroadcastReciever name NetworkReciver.java that executes when Internet is Connected or Disconnected. And it is working well.
But when app is closed from recent apps, then NetworkReciver.java does not executes in One Plus 6 Phone while it works proper in Samsung Phones.
I am not getting why the behavior is different in One Plus Device
My Code:
Manifest
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<receiver android:name=".NetworkReciever" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
NetworkReciever.java:
public class NetworkReciever extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Log.i("TAG", "Network REceiver Executed");
}
}
Problem:
NetworkReciever does not execute when app is closed from recent apps in One Plus Device.
Starting in Android N, the system does not send CONNECTIVITY_ACTION broadcasts to manifest receivers of applications targeting N+.
Explicit BroadcastReceivers registered via Context.registerReceiver() continue to receive these broadcasts.
Solution: See ConnectivityManager.CONNECTIVITY_ACTION deprecated
Apps targeting Android 7.0+ do not receive CONNECTIVITY_ACTION broadcasts if they register to receive them in their manifest, and processes that depend on this broadcast will not start.
So, if you want to do some work when internet connection is available. You can use Job scheduler or work manager.
For example, here is sample code for job scheduler.
public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
JobScheduler js =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo job = new JobInfo.Builder(
MY_BACKGROUND_JOB,
new ComponentName(context, MyJobService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setRequiresCharging(true)
.build();
js.schedule(job);
}
When the conditions for your job are met, your app receives a callback to run the onStartJob() method in the specified JobService.class
Android JobScheduler Sample
Also, registering broadcasts in the activity's onCreate and unregistering it in onDestroy will not work for your case as you will not receive the broadcast after the app is killed.
In Android Nougat, Android does not broadcast for network changes to manifest registered BroadcastReceiver.
From the Android Nogout Changes & Also mentioned in ConnectivityManager
Monitor for changes in connectivity
Apps targeting Android 7.0 (API level 24) and higher do not receive
CONNECTIVITY_ACTION broadcasts if they declare the broadcast receiver
in their manifest. Apps will still receive CONNECTIVITY_ACTION
broadcasts if they register their BroadcastReceiver with
Context.registerReceiver() and that context is still valid.
Solution
NetworkReciever does not execute when app is closed from recent apps
I don't know why you want to get network changes after the app is closed. But in this case you have to make some periodic task with WorkManager or JobScheduler. I suggest you use WorkManager because it will work for all devices. whether JobScheduler is available only for devices >= 21 version. Here is a good example for WorkManager (It is quite easy).
Background solution (execute when only you need)
public class MyWorker extends Worker {
#Override
public Worker.Result doWork() {
// get online status
boolean isOnline = isOnline(getApplicationContext());
// Indicate success or failure with your return value:
return Result.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
public boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
//should check null because in airplane mode it will be null
return (netInfo != null && netInfo.isConnected());
}
}
and schedule this Work at app start.
public static void scheduleWork() {
int TIME_INTERVAL_IN_SECONDS = 15;
PeriodicWorkRequest.Builder photoCheckBuilder = new PeriodicWorkRequest.Builder(MyWorker .class, TIME_INTERVAL_IN_SECONDS, TimeUnit.SECONDS);
PeriodicWorkRequest photoCheckWork = photoCheckBuilder.build();
WorkManager instance = WorkManager.getInstance();
if (instance != null) {
instance.enqueueUniquePeriodicWork("TAG", ExistingPeriodicWorkPolicy.KEEP, photoCheckWork);
}
}
Foreground solution (recommended)
Or if you just want to receive network changes when you app is live. You can below solution.
Register this receiver in your BaseActivity. or create one if you don't have yet any BaseActivity.
Register on onStart() and unregister on onStop(). Because you may not want to invoke your UI after onStop().
Here is BaseActivity.java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
/**
* Created by KHEMRAJ on 9/5/2018.
*/
public class BaseActivity extends AppCompatActivity {
NetworkReceiver receiver;
public boolean isOnline;
#Override
protected void onStart() {
super.onStart();
isOnline = isOnline(this);
// register network change receiver
receiver = new NetworkReceiver();
registerReceiver(receiver, new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
}
#Override
protected void onStop() {
super.onStop();
// unregister network change receiver
unregisterReceiver(receiver);
receiver = null;
}
public class NetworkReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
isOnline = isOnline(context);
Log.i("TAG", "Network REceiver Executed");
}
}
public boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
//should check null because in airplane mode it will be null
return (netInfo != null && netInfo.isConnected());
}
}
I suggested you WorkManger only, because I created a sample earlier
days with JobScheduler, EvernoteJobs,
AlarmManager, [JobService][7], and WorkManager. In which I started periodic task of 15 minutes with each of these. and
wrote logs of each in separate file when invoked.
Conclusion of this test was that. WorkManager and EvernoteJobs were
most efficient to do jobs. Now because EvernoteJobs will use
WorkManager from next version. So I came up with WorkManager.
Root cause:
From Android N OnePlus introduced a feature similar to Mi devices which prevent certain apps from auto-starting after reboot. I suspect that same feature is preventing your app to receive BroadcastReceiver as well.
Solution
Use AccessibilityService service in your app and ask user to turn on AccessibilityService for your app from Settings and boing doing this BroadcastReceiver in your app will work as expected.
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.
Here is how you can register your own AccessibilityService.
Create your custom AccessibilityService
public class MyAccessibilityService extends AccessibilityService {
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {//do nothing }
#Override
public void onInterrupt() { //do nothing}
}
Create configuration file my_accessibility_service.xml and add below code:
<?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"/>
Add permission to AndroidManifest.xml file:
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"/>
Add your AccessibilityService in AndroidManifest.xml file:
<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>
You done!
Below is method to check status of AccessibilityService:
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;
}
Note: I have not tried but it may help.
Broadcast Receiver is not supported in Oreo as manifest tag, you must have to register it as a Service/ Activity with context.registerReceiver(). Or you use the WorkManager to schedule something for specific network conditions.
use this code in OnCreate
NetworkReciever receiver = NetworkReciever ()
IntentFilter filter = new IntentFilter();
filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(receiver, filter);
don't forget to unregister it in onDestroy
#Override
protected void onDestroy() {
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
super.onDestroy();
}
and delete this from Manifest
<receiver android:name=".NetworkReciever" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
I'm posing this as Q&A style because I found this idea working. And it's a fix to the hard problem to crack for beginners with Android.
Google has deprecated registering Broadcast Receiver into manifest like this below from API Level 26+ ( Except Some )
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.net.wifi.STATE_CHANGE" />
</intent-filter>
</receiver>
But, If one wants to receive particular device state changes like Internet Connectivity Changes (Which isn't allowed) while the app is in background and if it's important for any feature of his application, what should he do?
When I was going through the documentation, My eyes got stuck here :
Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if you register within
an Activity context, you receive broadcasts as long as the activity is
not destroyed. If you register with the Application context, you
receive broadcasts as long as the app is running.
That practically means if I can hold a Context, the broadcast-receiver registered with it will run in the background.
For doing that, a Service will be the best practice.
This is below code for a STICKY_SERVICE which is started again after killed and thus the context remains valid.
AlwaysOnService.class
package app.exploitr.auto;
import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;
public class AlwaysOnService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
registerReceiver(new ClickReceiver(), new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
return Service.START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onLowMemory() { // rem this if you want it always----
stopSelf();
super.onLowMemory();
}
}
Now, the receiver which actually does things :
ClickReceiver.class
package app.exploitr.auto;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.Objects;
public class ClickReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, Intent intent) {
switch (Objects.requireNonNull(intent.getAction())) {
case AutoJob.NOTIFICATION_CANCEL_TAG:
System.out.println("Not related");
break;
case AutoJob.LOGIN_CANCEL_TAG:
System.out.println("Not related");
break;
case "android.net.conn.CONNECTIVITY_CHANGE":
System.out.println("Oops! It works...");
break;
}
}
}
Launch Code From Any Activity Class
private void setUpBackOffWork() {
if (DataMan.getInstance(getBaseContext()).getPeriodic()) {
AutoJob.schedulePeriodic();
//Not related
}
if (DataMan.getInstance(getBaseContext()).getPureAutoLogin()) {
startService(new Intent(this, AlwaysOnService.class));
}
}
So my target was to Login into my isp automatically when I turn up my android's WiFi, and the code works smooth. It doesn't fail ever (It's running for 7 hours and 37 minutes till now and working well | not across reboots).
To keep the receiver running across reboots, try manifest registerable BOOT_COMPLETED actions. It works just like the old one.
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
Update 1
Now, as Google took one step to limit background execution & as a result you've also to make the service a foreground service. So, the procedure goes below.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, new Intent(THIS_SERVICE_CLASS_NAME.this, ACTIVITY_TO_TARGET.class), 0);
/*Handle Android O Notifs as they need channel when targeting 28th SDK*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel(
"download_check_channel_id",
"Channel name",
NotificationManager.IMPORTANCE_LOW);
if (notificationManager != null) {
notificationManager.createNotificationChannel(notificationChannel);
}
builder = new Notification.Builder(this.getBaseContext(), notificationChannel.getId())
.setContentTitle("Hi! I'm service")
.setContentIntent(pendingIntent)
.setOngoing(true);
notification = builder.build();
startForeground("StackOverflow".length(), notification);
}
return START_STICKY;
}
This also applies to Xamarin Android. The Play Store demanded to upgrade my apps's SDK to 8.0 Oreo, and a bunch of stuff stopped working on it.
Microsoft's documentation on Broadcast Receivers is quite confusing:
Apps that target Android 8.0 (API level 26) or higher may not statically register for an implicit broadcast. Apps may still statically register for an explicit broadcast. There is a small list of implicit broadcasts that are exempt from this restriction.
Even Google's official docs are quite inscrutable.
On Xamarin Android it used to be enough to follow this pattern:
[BroadcastReceiver]
[IntentFilter(new string[] {MyReceiver.MyAction})]
public class MyReceiver : BroadcastReceiver
{
public const String MyAction = "com.mytest.services.MyReceiver.MyAction";
public override void OnReceive (Context context, Intent intent)
{
// ...
}
}
The IntentFilter annotation instructs the compiler to add the receiver and intent filters registrations to the Manifest file during the build process. But from target SDKs v8.0 (Oreo/API 26) and above Android ignores these configurations on Manifest (except some system implicit actions). So this means that the IntentFilter annotations only works for those exceptions, and to make your broadcast receivers receive broadcasts it is required to register them on execution time:
#if DEBUG
[Application(Debuggable=true)]
#else
[Application(Debuggable=false)]
#endif
public class MyApplication: Application
{
public override void OnCreate ()
{
base.OnCreate ();
Context.RegisterReceiver(new MyReceiver(), new IntentFilter(MyReceiver.MyAciton));
}
}
It is also possible to register a receiver only for the lifecycle of an Activity, as explained by #Toaster. You can keep sending broadcasts normally:
// ...
ApplicationContext.SendBroadcast(new Intent(MyReceiver.MyAction));
// ...
Problem:
So the problem is that I have an app which sends a request to our backend whenever WiFi is connected (with the connected SSID and other info) or when it is disconnected (over the mobile network). However with the changes in Android 7/N and above, CONNECTIVITY_CHANGE and CONNECTIVITY_ACTION no longer work in the background. Now in most cases people misuse this broadcast and as such I can completely understand why the change was made. However, I have no idea how to solve this problem in the current state.
Now I'm not at all much of an Android developer (this is for a Cordova plugin) so I'm counting on you guys!
Expected behavior:
App is woken up and request is sent whenever WiFi switches connectivity, even when app is killed/in background.
Current behavior:
App only sends request when the app is in the foreground.
Tried so far:
So far I've moved the implicit intent to listen to CONNECTIVITY_ACTION from the manifest to manually registering it in the main part of the app (plugin). This makes it work as long as the app is in memory but not on cold boot or actual background
Already looked at:
Most answers talk about using scheduled jobs to substitute for the missing broadcast. I see how this works for, for example, retrying a download or similar, but not for my case (but please correct me if I'm wrong). Below are the SO posts I've already looked at:
Detect connectivity changes on Android 7.0 Nougat when app is in foreground
ConnectivityManager.CONNECTIVITY_ACTION deprecated
Detect Connectivity change using JobScheduler
Android O - Detect connectivity change in background
Nougat and Above:
We have to use JobScheduler and JobService for Connection Changes.
All I can divide this into three steps.
Register JobScheduler inside activity. Also, Start JobService(
Service to handle callbacks from the JobScheduler. Requests scheduled
with the JobScheduler ultimately land on this service's "onStartJob"
method.)
public class NetworkConnectionActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network_connection);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
scheduleJob();
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void scheduleJob() {
JobInfo myJob = new JobInfo.Builder(0, new ComponentName(this, NetworkSchedulerService.class))
.setRequiresCharging(true)
.setMinimumLatency(1000)
.setOverrideDeadline(2000)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.build();
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(myJob);
}
#Override
protected void onStop() {
// A service can be "started" and/or "bound". In this case, it's "started" by this Activity
// and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call
// to stopService() won't prevent scheduled jobs to be processed. However, failing
// to call stopService() would keep it alive indefinitely.
stopService(new Intent(this, NetworkSchedulerService.class));
super.onStop();
}
#Override
protected void onStart() {
super.onStart();
// Start service and provide it a way to communicate with this class.
Intent startServiceIntent = new Intent(this, NetworkSchedulerService.class);
startService(startServiceIntent);
}
}
The service to start and finish the job.
public class NetworkSchedulerService extends JobService implements
ConnectivityReceiver.ConnectivityReceiverListener {
private static final String TAG = NetworkSchedulerService.class.getSimpleName();
private ConnectivityReceiver mConnectivityReceiver;
#Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service created");
mConnectivityReceiver = new ConnectivityReceiver(this);
}
/**
* When the app's NetworkConnectionActivity is created, it starts this service. This is so that the
* activity and this service can communicate back and forth. See "setUiCallback()"
*/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand");
return START_NOT_STICKY;
}
#Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "onStartJob" + mConnectivityReceiver);
registerReceiver(mConnectivityReceiver, new IntentFilter(Constants.CONNECTIVITY_ACTION));
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "onStopJob");
unregisterReceiver(mConnectivityReceiver);
return true;
}
#Override
public void onNetworkConnectionChanged(boolean isConnected) {
String message = isConnected ? "Good! Connected to Internet" : "Sorry! Not connected to internet";
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
}
Finally, The receiver class which checks the network connection
changes.
public class ConnectivityReceiver extends BroadcastReceiver {
private ConnectivityReceiverListener mConnectivityReceiverListener;
ConnectivityReceiver(ConnectivityReceiverListener listener) {
mConnectivityReceiverListener = listener;
}
#Override
public void onReceive(Context context, Intent intent) {
mConnectivityReceiverListener.onNetworkConnectionChanged(isConnected(context));
}
public static boolean isConnected(Context context) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
public interface ConnectivityReceiverListener {
void onNetworkConnectionChanged(boolean isConnected);
}
}
Don't forget to add permission and service inside manifest file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourpackagename">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- Always required on api < 21, needed to keep a wake lock while your job is running -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- Required on api < 21 if you are using setRequiredNetworkType(int) -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- Required on all api levels if you are using setPersisted(true) -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".connectivity.NetworkConnectionActivity"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Define your service, make sure to add the permision! -->
<service
android:name=".connectivity.NetworkSchedulerService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>
</manifest>
Please refer below links for more info.
https://github.com/jiteshmohite/Android-Network-Connectivity
https://github.com/evant/JobSchedulerCompat
https://github.com/googlesamples/android-JobScheduler
https://medium.com/#iiro.krankka/its-time-to-kiss-goodbye-to-your-implicit-broadcastreceivers-eefafd9f4f8a
The best way to grab Connectivity change Android Os 7 and above is register your ConnectivityReceiver broadcast in Application class like below, This helps you to get changes in background as well until your app alive.
public class MyApplication extends Application {
private ConnectivityReceiver connectivityReceiver;
private ConnectivityReceiver getConnectivityReceiver() {
if (connectivityReceiver == null)
connectivityReceiver = new ConnectivityReceiver();
return connectivityReceiver;
}
#Override
public void onCreate() {
super.onCreate();
registerConnectivityReceiver();
}
// register here your filtters
private void registerConnectivityReceiver(){
try {
// if (android.os.Build.VERSION.SDK_INT >= 26) {
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
//filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
//filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
//filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(getConnectivityReceiver(), filter);
} catch (Exception e) {
MLog.e(TAG, e.getMessage());
}
}
}
And then in manifest
<application
android:name=".app.MyApplication"/>
Here is your ConnectivityReceiver.java
public class ConnectivityReceiver extends BroadcastReceiver {
#Override
public void onReceive(final Context context, final Intent intent) {
MLog.v(TAG, "onReceive().." + intent.getAction());
}
}
That's how i did it. I have created a IntentService and in onCreate method and I have registered networkBroadacst which check for internet connection.
public class SyncingIntentService extends IntentService {
#Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
networkBroadcast=new NetworkBroadcast();
registerReceiver(networkBroadcast,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
#Override
public int onStartCommand(#Nullable Intent intent, int flags, int startId) {
onHandleIntent(intent);
return START_STICKY;
}
}
This is my broadcast class
public class NetworkBroadcast extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Constants.isInternetConnected(context)) {
// Toast.makeText(context, "Internet Connect", Toast.LENGTH_SHORT).show();
context.startService(new Intent(context, SyncingIntentService.class));
}
else{}
}
}
In this way you can check internet connection in whether your app is in foreground or background in nougat.
Below is excerpt from documentation
Apps targeting Android 7.0 (API level 24) and higher do not receive
CONNECTIVITY_ACTION broadcasts if they declare the broadcast receiver
in their manifest. Apps will still receive CONNECTIVITY_ACTION
broadcasts if they register their BroadcastReceiver with
Context.registerReceiver() and that context is still valid.
So you will get this Broadcast till your context is valid in Android N & above by explicitly registering for same.
Boot Completed:
You can listen android.intent.action.BOOT_COMPLETED broadcast
you will need this permission for same.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
App Killed Scenario:
You are not going to receive it.
That is very much expected and due to various reasons
Android Oreo has limitations on running services in background, so you may face this on O devices
Doze mode on Android Marshmallow onwards can cause this, it will stop all network operations itself & take away CPU wake locks
Though Doze mode have one mechanism for requesting whitelisting of apps, this might be useful for you.
Another approach which is simpler and easier when you use registerNetworkCallback (NetworkRequest, PendingIntent):
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
builder.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
Intent intent = new Intent(this, SendAnyRequestService.class);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
if (connectivityManager != null) {
NetworkRequest networkRequest = builder.build();
connectivityManager.registerNetworkCallback(networkRequest, pendingIntent);
}
Which is SendAnyRequestService.class is your service class, and you can call your API inside it.
This code work for Android 6.0 (API 23) and above
Ref document is here
I have tried various ways to achieve this, but my service eventually gets killed.
I want to use AlarmManager to trigger a class every one hour. Even if the device is sleeping, it should sent a flashing LED alert, vibration or sound. In any case, it should run forever.
I have noticed that Whatsapp is always running, even though I kill all the running apps and clear the memory, put the device to sleep, and still Whatsapp receive messages and alerts me. How are they doing it? I want to do the same with my app.
NOTE: NOW THIS ANSWER IS ONLY VALID FOR ANDROID 7 AND BELOW. SINCE ANDROID 8 GOOGLE HAS CHANGED HOW BACKGROUND TASKS ARE HANDLED
Since I posted this question, I have implemented two different approaches to this solution into multiple apps.
APPROACH 1
This extract is from an app where I use push notifications, which need instant wake up calls for the device. Here what I do is
use WAKE_LOCK permission and
use a Wakelocker abstract class
use it in an Activity as needed:
Manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
WakeLocker class:
public abstract class WakeLocker {
private static PowerManager.WakeLock wakeLock;
public static void acquire(Context context) {
if (wakeLock != null) wakeLock.release();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.ON_AFTER_RELEASE, "WakeLock");
wakeLock.acquire();
}
public static void release() {
if (wakeLock != null) wakeLock.release(); wakeLock = null;
}
}
Activity class example:
private final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Waking up mobile if it is sleeping
WakeLocker.acquire(getApplicationContext());
// do something
WakeLocker.release();
}
APPROACH 2
Best when you want to give Android control over wake up, and can live with periodically waking up your code. Simply use an AlarmManager to invoke a Service class at regular intervals. Here is some code from my LifeLog24 app:
MainActivity
Intent ll24 = new Intent(context, AlarmReceiverLifeLog.class);
PendingIntent recurringLl24 = PendingIntent.getBroadcast(context, 0, ll24, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.RTC_WAKEUP, first_log.getTime(), AlarmManager.INTERVAL_HOUR, recurringLl24); // Log repetition
Alarm Class
public class AlarmReceiverLifeLog extends BroadcastReceiver {
private static final String TAG = "LL24";
static Context context;
#Override
public void onReceive(Context context, Intent intent) {
Log.v(TAG, "Alarm for LifeLog...");
Intent ll24Service = new Intent(context, LifeLogService.class);
context.startService(ll24Service);
}
}
and LifeLogService.class is where I do my stuff. Alarm wakes up every hour in this case and triggers the BroadcastReceiver which in return runs the service. There is more to it, to make sure service is not run twice and so on, but you get the point how it is done. And AlarmManager is actually the best way to do it since you don't worry about battery usage, etc. and Android takes care of waking up your Service at regular intervals.
It is very simple.
steps:
1.create a Service class.
2.create a BroadcastReceiver class
3.call BroadReceiver in onDestroy method of service
4.In onReceive method of BroadReceiver class start service once again.
Here's the code
Manifest file:`
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".LauncherActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".utilities.NotificationService"
android:enabled="true">
</service>
<receiver
android:name=".utilities.RestartService"
android:enabled="true"
android:exported="true"
android:label="RestartServiceWhenStopped"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="RestartService" />
</intent-filter>
</receiver>
</application>
`
Service class
public class NotificationService extends Service {
public NotificationService() {
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
Intent restartService = new Intent("RestartService");
sendBroadcast(restartService);
}
}
BroadcastReceiver class
public class RestartService extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context,NotificationService.class));
}
}
Follow these easy steps to keep servce alive forever in android device.
1. Call a service by using alarm manager.
2. return START_STICKY in onStart method.
3. In on destroy call the alarm manager and restart service by using startService method.
4.(Optional)Repeat the point number 3 in onTaskRemoved method.
Request partial WakeLock.
<uses-permission android:name="android.permission.WAKE_LOCK" />
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "My Tag");
mWakeLock.acquire();
onStartCommand retrun START_STICKY :
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
You can use a function startForeground(int, Notification) see here and here
A started service can use the startForeground(int, Notification) API
to put the service in a foreground state, where the system considers
it to be something the user is actively aware of and thus not a
candidate for killing when low on memory. (It is still theoretically
possible for the service to be killed under extreme memory pressure
from the current foreground application, but in practice this should
not be a concern.)