Skip to content
This repository has been archived by the owner on Dec 17, 2023. It is now read-only.

Android 8+ Not allowed to start service: app is in background #2

Closed
nazmulidris opened this issue Jun 1, 2020 · 5 comments
Closed
Assignees
Labels

Comments

@nazmulidris
Copy link
Member

nazmulidris commented Jun 1, 2020

Repro steps:

  1. Install the app on a Android 8+ device, and make sure to add the quick tile icon to the notification shade area.
  2. Reboot the device while the power cable is connected and charging.
  3. The following exception will be logged, since Awake should start up but it does not! Due to this error.
I: Late-enabling -Xcheck:jni
E: Unknown bits set in runtime_flags: 0x8000
D: onCreate: 
D: registerReceiver: PowerConnectionReceiver
E: Service can't be started, because app is current in background
E: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.r3bl.stayawake/.MyTileService (has extras) }: app is in background uid UidRecord{cd8bb06 u0a263 SVC  idle change:uncached procs:1 seq(0,0,0)}
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1616)
        at android.app.ContextImpl.startService(ContextImpl.java:1571)
        at android.content.ContextWrapper.startService(ContextWrapper.java:669)
        at com.r3bl.stayawake.MyTileService.startService(MyTileService.java:94)
        at com.r3bl.stayawake.MyTileService.coldStart(MyTileService.java:129)
        at com.r3bl.stayawake.MyTileService.onCreate(MyTileService.java:89)
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:3953)
        at android.app.ActivityThread.access$1500(ActivityThread.java:219)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1875)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
@nazmulidris nazmulidris self-assigned this Jun 1, 2020
@nazmulidris nazmulidris added the bug label Jun 1, 2020
@nazmulidris
Copy link
Member Author

nazmulidris commented Jun 1, 2020

The problem:

Basically it is no longer possible for a background app to start a service while it is in the background. Awake is in the background after the app restarts, and the TileService is created. This correctly registers the PowerConnectionReceiver, but it does not allow the service to be started. Only when the user interacts with the tile icon or the MainActivity will the process be marked as "foreground" and thus will be able to start the service.

This link has more information on what has changed:
https://developer.android.com/about/versions/oreo/background#migration

Here's more info on the problem:
https://medium.com/@berriz_/service-and-boot-completed-on-android-o-6a389eae50f1

@nazmulidris
Copy link
Member Author

nazmulidris commented Jun 1, 2020

DOES NOT WORK Having tried this, it doesn't work. The JobScheduler ends up getting into the same issue that MyTileService got into as well (when I scheduled a job from the PowerConnectionReceiver). Also, the BOOT_COMPLETED receiver never runs!

One solution might be to use the JobScheduler & BOOT_COMPLETED BroadcastReceiver in order to replicate the functionality that I desire (which is to have the app in the background start a service, when the device is connected to power).

Tutorials on this:
https://www.vogella.com/tutorials/AndroidTaskScheduling/article.html#exercise-preparation

This requires the creation of the following

  1. JobService subclass that actually starts/stops the service based on whether the device is connected to power or not.
  2. BOOT_COMPLETED BroadcastReceiver that actually schedules the job when the device is rebooted.
  3. Both of these need to be registered in the AndroidManifest.xml file.

Here's an example.

AndroidManifest.xml

    <!-- JobScheduler service that queues the job to start when the device is charging. -->
    <!-- More info: https://developer.android.com/reference/android/app/job/JobService -->
    <service
        android:name=".MyJobService"
        android:label="Stay Awake Service that runs when device charging"
        android:permission="android.permission.BIND_JOB_SERVICE"
    />

    <!-- BroadcastReceiver that starts schedules the job above when device boots. -->
    <receiver android:name=".MyBootCompletedBroadcastReceiver">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
      </intent-filter>
    </receiver>

MyJobService.java

public class MyJobService extends JobService {

public static void scheduleJob(Context context) {
  d(TAG, "scheduleJob: create JobInfo and hand it to the JobScheduler");
  ComponentName serviceComponent = new ComponentName(context, MyJobService.class);
  JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(0, serviceComponent);
  jobInfoBuilder.setRequiresCharging(true);
  JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
  jobScheduler.schedule(jobInfoBuilder.build());
}

/**
 * https://developer.android.com/reference/android/app/job/JobService#onStartJob(android.app.job.JobParameters)
 */
@Override public boolean onStartJob(JobParameters params) {
  d(TAG, "onStartJob: Start Service");
  MyTileService.startService(getApplicationContext());
  return true;
}

/**
 * https://developer.android.com/reference/android/app/job/JobService#onStartJob(android.app.job.JobParameters)
 */
@Override public boolean onStopJob(JobParameters params) {
  d(TAG, "onStopJob: Stop Service");
  MyTileService.stopService(getApplicationContext());
  return false;
}
}

MyBootCompletedBroadcastReceiver.java

public class MyJobService extends JobService {

public static void scheduleJob(Context context) {
  d(TAG, "scheduleJob: create JobInfo and hand it to the JobScheduler");
  ComponentName serviceComponent = new ComponentName(context, MyJobService.class);
  JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(0, serviceComponent);
  jobInfoBuilder.setRequiresCharging(true);
  JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
  jobScheduler.schedule(jobInfoBuilder.build());
}

/**
 * https://developer.android.com/reference/android/app/job/JobService#onStartJob(android.app.job.JobParameters)
 */
@Override public boolean onStartJob(JobParameters params) {
  d(TAG, "onStartJob: Start Service");
  MyTileService.startService(getApplicationContext());
  return true;
}

/**
 * https://developer.android.com/reference/android/app/job/JobService#onStartJob(android.app.job.JobParameters)
 */
@Override public boolean onStopJob(JobParameters params) {
  d(TAG, "onStopJob: Stop Service");
  MyTileService.stopService(getApplicationContext());
  return false;
}
}

@nazmulidris
Copy link
Member Author

nazmulidris commented Jun 1, 2020

DOES NOT WORK The BOOT_COMPLETED broadcast receiver does not run! Even when I enqueue the work from the PowerConnectionReceiver it ends up running into the same issue as MyTimeService and the failed solution above.

https://medium.com/@berriz_/service-and-boot-completed-on-android-o-6a389eae50f1

AndroidManifest.xml

    <!-- JobScheduler service that queues the job to start when the device is charging. -->
    <!-- More info: https://developer.android.com/reference/android/app/job/JobService -->
    <service
        android:name=".MyJobService"
        android:icon="@drawable/ic_stat_visibility"
        android:label="@string/tile_uninstalled_text"
        android:permission="android.permission.BIND_JOB_SERVICE"
    />

    <!-- BroadcastReceiver that starts schedules the job above when device boots. -->
    <receiver android:name=".MyBootReceiver">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
      </intent-filter>
    </receiver>

MyJobService.java

public class MyJobService extends JobIntentService {

// JobIntentService
// More info: https://developer.android.com/reference/androidx/core/app/JobIntentService
// More info: https://medium.com/@berriz_/service-and-boot-completed-on-android-o-6a389eae50f1
public static final int JOB_ID = 0x01;

public static void enqueueWork(Context context) {
  d(TAG, "enqueueWork: hand work over to JobIntentService");
  enqueueWork(context, MyJobService.class, JOB_ID, new Intent());
}

@Override protected void onHandleWork(@NonNull Intent intent) {
  d(TAG, "onHandleWork: Start Service");
  MyTileService.startService(getApplicationContext());
}
}

MyBootReceiver.java

public class MyBootReceiver extends BroadcastReceiver {
@Override public void onReceive(Context context, Intent intent) {
  d(TAG, "onReceive: MyBootCompletedBroadcastReceiver " + intent.getAction());
  if (intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
    MyJobService.enqueueWork(context.getApplicationContext());
  }
  d(TAG, "onReceive: scheduleJob()");
}
}

@nazmulidris
Copy link
Member Author

nazmulidris commented Jun 1, 2020

After

  1. a phone running Awake is restarted, and
  2. the device is connected to power,

If the

  1. quick tile is already added to the notification shade,

Then

  1. onCreate is called on MyTileService and it craps out w/ the IllegalStateException when trying to start the service (while the app is in the background).

The only thing that I can think of is showing the user a Toast asking them to either launch the app, or activate the Awake quick tile.

@nazmulidris
Copy link
Member Author

The following line of code fixes this issue. Turns out that the exception was arising because I was trying to call context.startService(), instead, in onCreate(), which is already a created service, I can simply call the following:

  if (isCharging(this)) {
    commandStart();
  }

And this resolves the issue!

nazmulidris added a commit that referenced this issue Jun 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

1 participant