1

I am developing app which sends SMS on active call ends.

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx.xxxx.xxxx.xxxx">

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<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=".MainActivity"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name=".MonitorService"
        android:enabled="true"
        android:exported="true" />
    <service
        android:name=".SendText"
        android:exported="false" />

    <receiver
        android:name=".BootReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
</application>

</manifest>

I have taken all the runtime permissions if its above Marshmallow too from MainActivity.

My Receiver :

public class BootReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
    Log.d("BootTest : ", "\nOnBootReceiver - Received a broadcast!");
    Toast.makeText(context, "InstaPromo Is Ready !!", Toast.LENGTH_LONG).show();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
    {
        context.startForegroundService(new Intent(context, MonitorService.class));
    }
    else
    {
        context.startService(new Intent(context, MonitorService.class));
    }
}
}

Implemented receiver runtime as :

public class MonitorService extends Service
{
// Notification variables

private BroadcastReceiver mCallBroadcastReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        String action = intent.getAction();

        if (action.equalsIgnoreCase("android.intent.action.PHONE_STATE"))
        {
            if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING))
            {
                Log.d("RECEIVER X: ", "INCOMING CALL...");
            }
            if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_IDLE))
            {
                Log.d("RECEIVER X: ", "CALL ENDS HERE...");
                Intent Dispatcher = new Intent(context, SendText.class);
                startService(Dispatcher);
            }
            if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_OFFHOOK))
            {
                Log.d("RECEIVER X: ", "ACTIVE CALL GOING ON...");
            }
        }
    }
};

public MonitorService() { }

@Override
public void onCreate()
{
    super.onCreate();

    // created notification here
    // also registered broadcast receiver here

    mBuilder.setChannelId(NOTIFICATION_CHANNEL_ID);
    startForeground(17, mBuilder.build());
}

@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
    Log.d("WatchMan : ", "\nmCallBroadcastReceiver Listening....");

    //return super.onStartCommand(intent, flags, startId);

    return START_STICKY;
}

@Override
public void onDestroy()
{
    this.unregisterReceiver(mCallBroadcastReceiver);
    Log.d("WatchMan : ", "\nDestroyed....");
    Log.d("WatchMan : ", "\nWill be created again....");
}

@Override
public IBinder onBind(Intent intent)
{
    // TODO: Return the communication channel to the service.
    throw new UnsupportedOperationException("Not yet implemented");
}
}
  • Broadcastreceiver works fine from service class; even if App removed from recent app list, But If removed from recents then on next boot_complete it never calls receiver again. If App is not removed from recents... then on every boot_complete / quick boot it gets triggered and broadcast-receiver works perfectly. I have tried android:exclude_from_recents..., But it is not the way to achieve it.

Can someone please help me to resolve this situation. Thanks

Update 2

I have studied Firebase JobDispatcher documentation over github and used it as belows :

  1. Implemented dependancy in gradle project file
  2. Success in sync thenafter
  3. Created a JobService
  4. Created a job and scheduled it.
  5. It is getting triggered on boot_completed too..

public class MyJobService extends JobService
{
@Override
public boolean onStartJob(JobParameters job)
{
    // Do some work here

    Log.d("MY_JOB :", "STARTED HERE...  \n");

    return false;

    // Answers the question: "Is there still work going on?"
}

@Override
public boolean onStopJob(JobParameters job)
{
    Log.d("MY_JOB :", "STOPPED HERE...  \n");

    return true;

    // Answers the question: "Should this job be retried?"
}
}

        FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(MainActivity.this));

    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")
            // one-off job
            .setRecurring(false)
            // don't persist past a device reboot
            .setLifetime(Lifetime.FOREVER)
            // start between 0 and 60 seconds from now
            .setTrigger(Trigger.executionWindow(10, 10+2))
            // 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
            .setConstraints(
                    // only run when device is idle
                    Constraint.DEVICE_IDLE
            )
            .setExtras(myExtrasBundle)
            .build();

    dispatcher.mustSchedule(myJob);

How should i schedule it just for running once on reboot/boot_complete

  1. I want to recur only once stopped.. : setRecurring(false), IS IT OKAY to false or true it should be true?
  2. setLifetime(Lifetime.UNTIL_NEXT_BOOT) SET it to FOREVER THIS IS OKAY
  3. setTrigger(Trigger.executionWindow(0, 60)) NOT GETTING THIS
  4. setReplaceCurrent(false) YES ITS OKAY.
  5. retry with exponential backoff What is This?

How to set these tags to get started on BOOT / REBOOT and only once, try if fails to start, do not start again ..?

Khaled Lela
  • 7,831
  • 6
  • 45
  • 73
sandhya sasane
  • 1,334
  • 12
  • 19
  • what is your target sdk version – Rohit Sharma Aug 05 '18 at 05:17
  • @RohitSharma, min sdk = 21, compile sdk 26, target sdk 26; – sandhya sasane Aug 05 '18 at 05:41
  • Can you try to keep your targetSdkVersion at 25 or lower, and check feed back, Hack of [CommonsWare](https://commonsware.com/blog/2017/04/11/android-o-implicit-broadcast-ban.html) for similar case. – Khaled Lela Aug 05 '18 at 05:59
  • It is running on reboot too.. ( even if removed from recents ) below And above Oreo, Only in Oreo if removed from recents then it never gets called on reboot untill the app is opened again... GOOGLE MADE COMPULSORY TARGET SDK 26 ... Its a couple of fortnights now for uploading to play store – sandhya sasane Aug 05 '18 at 06:03
  • Did you try `JobIntentService` mentioned with my [answer](https://stackoverflow.com/a/51691750/1283715) – Khaled Lela Aug 05 '18 at 06:11
  • IntentService and hence JobIntentService is meant to perform tasks that are not very long. In your case I'm assuming your service is going to run all the time in the background. Things like these are most suited in Services (that you have already implemented). So just for restarting the service across reboots use JobDispatcher as it is the recommended way and is also supported back to the API level 14 which I have mentioned in my answer. – Muhammad Muzammil Aug 05 '18 at 07:54
  • @MuhammadMuzammil `JobIntentService` is meant to be a replacement for the `IntentService/WakefulBroadcastReceive` then it's the **correct choice** here, Check [CommonsWare answer](https://stackoverflow.com/a/48268749/1283715) – Khaled Lela Aug 05 '18 at 09:07
  • Will everyone please try to delete unwanted comments of our own and make this a bit nice to understand whoever is facing this problem...? I do not want to delete others comments... I am going for mine.. – sandhya sasane Aug 06 '18 at 16:39

2 Answers2

1

Use FirebaseJobDispatcher to restart your service after every boot. For more on JobDispatcher please see this.

Muhammad Muzammil
  • 1,313
  • 1
  • 12
  • 28
1
  • If the app is first installed or force stopped by user, BroadcastReceviers will not run until user runs the app manually at least once.

  • AS per doc & answer

    • Note that an application's stopped state is not the same as an Activity's stopped state. The system manages those two stopped states separately.

    • Applications are in a stopped state when they are first installed but are not yet launched and when they are manually stopped by the user (in Manage Applications). (They mean force stop an app)

  • Htc devices add com.htc.intent.action.QUICKBOOT_POWERON >> From this answer.

    <receiver android:enabled="true" android:name=".receivers.BootUpReceiver">
        <intent-filter>
            <category android:name="android.intent.category.DEFAULT" />
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
            <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
            <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
         </intent-filter>
    </receiver>
    

Starting from Android O,

  • You can not start a service from a background app without being blessed with an exception: java.lang.IllegalStateException: Not allowed to start service Intent (my_service) : app is in background If you still need to launch a service at device start up, you can now use the new JobIntentService.

Add your BroadcastReceiver and JobIntentService to your manifest

<receiver android:name=".BootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
</receiver>

<service android:name=".MyService"
         android:permission="android.permission.BIND_JOB_SERVICE"/>

Enqueue work for your JobIntentService:

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            MyService.enqueueWork(context, new Intent());
        }
    }

}

Define your JobIntentService:

public class MyJobIntentService extends JobIntentService {

    public static final int JOB_ID = 0x01;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, MyJobIntentService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // your code
    }

/**
 * This will be called if the JobScheduler has decided to stop this job.  The job for
 * this service does not have any constraints specified, so this will only generally happen
 * if the service exceeds the job's maximum execution time.
 *
 * @return True to indicate to the JobManager whether you'd like to reschedule this work,
 * false to drop this and all following work. Regardless of the value returned, your service
 * must stop executing or the system will ultimately kill it.  The default implementation
 * returns true, and that is most likely what you want to return as well (so no work gets
 * lost).
 */
public boolean onStopCurrentWork() {
    return true;
}

}
Khaled Lela
  • 7,831
  • 6
  • 45
  • 73
  • 1) Forced stopped is different from removing it from recents 2) Oreo onwards if you put multiple actions on the receiver in manifest it never calls it; just need to keep allowed extempted intent actions and whichever are not allowed implement it in runtime... You can take a look on code. It is strictly as per documentation – sandhya sasane Aug 05 '18 at 06:12
  • YES..., IT is noticeable ; But How to implement register and unregister runtime receiver in JobIntentService ...? Is it allowed? – sandhya sasane Aug 05 '18 at 06:14