title: Android WorkManager: how to handle ongoing work, like Bluetooth scanning
body: <p>With the recent restrictions to <a href="https://developer.android.com/about/versions/oreo/background#services" rel="nofollow noreferrer">background Services</a> and <a href="https://developer.android.com/guide/components/broadcast-exceptions" rel="nofollow noreferrer">implicit Broadcasts</a>, Android devs are left with <a href="https://developer.android.com/reference/android/app/job/JobScheduler" rel="nofollow noreferrer">JobScheduler</a>, and, at a higher level, <a href="https://developer.android.com/topic/libraries/architecture/workmanager/" rel="nofollow noreferrer">WorkManager</a> to schedule background tasks. </p>
  
  <p>The Worker class for WorkManager is simple enough, but I'm a bit confused about the best way to implement ongoing work as opposed to one-off work. For our example, let's consider Bluetooth Low Energy scanning, but the same concern applies to all ongoing, indeterminate work.</p>
  
  <p>Something like this obviously doesn't work:</p>
  
  <pre><code>public class MyWorker extends Worker {
  
      private BluetoothLeScanner mBluetoothLeScanner;
  
      @Override
      public Worker.Result doWork() {
          mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
  
          // Pretend there's some bluetooth setup here
          // ...
  
          mBluetoothLeScanner.startScan( .. ,  .. , .. );
          return Result.SUCCESS;
      }
  
  }
  </code></pre>
  
  <p>Above we start scanning, then fall out of scope immediately, so scanning will not continue.</p>
  
  <p>We can use wait()/notify() to get around this, but it feels very dirty. Something like this...</p>
  
  <pre><code>public class MyWorker extends Worker {
  
      private BluetoothLeScanner mBluetoothLeScanner;
      private final Object mLock = new Object();
      private Handler mBackgroundHandler;
  
      private Handler getBackgroundHandler() {
          if (mBackgroundHandler == null) {
              HandlerThread thread = new HandlerThread("background");
              thread.start();
              mBackgroundHandler = new Handler(thread.getLooper());
          }
          return mBackgroundHandler;
      }
  
      @Override
      public Worker.Result doWork() {
          getBackgroundHandler().post(new Runnable() {
              @Override
              public void run() {
                  mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
                  // Pretend there's some bluetooth setup here
                  // ...
                  mBluetoothLeScanner.startScan( .. ,  .. , mScanCallback);
              }
          });
  
          getBackgroundHandler().postDelayed(new Runnable() {
              @Override
              public void run() {
                  mBluetoothLeScanner.stopScan(mScanCallback);
                  synchronized (mLock) {
                      mLock.notify();
                  }
              }
          },  60 * 1000); //stop after a minute
  
          try {
              synchronized (mLock) {
                  mLock.wait();
              }
          }
          catch (InterruptedException ex) {
              Thread.currentThread().interrupt();
          }
  
          return Result.SUCCESS;
      }
  
      private ScanCallback mScanCallback = new ScanCallback() {
          @Override
          public void onScanResult(int callbackType, ScanResult result) {
              super.onScanResult(callbackType, result);
              //We found an advertisement
              mBluetoothLeScanner.stopScan(mScanCallback);
              synchronized (mLock) {
                  mLock.notify();
              }
          }
  
          @Override
          public void onBatchScanResults(List&lt;ScanResult&gt; results) {
              super.onBatchScanResults(results);
          }
  
          @Override
          public void onScanFailed(int errorCode) {
              super.onScanFailed(errorCode);
              mBluetoothLeScanner.stopScan(mScanCallback);
              synchronized (mLock) {
                  mLock.notify();
              }
          }
      };
  
      @Override
      public void onStopped(boolean cancelled) {
          if (mBackgroundHandler != null) {
              if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                  mBackgroundHandler.getLooper().quitSafely();
              } else {
                  mBackgroundHandler.getLooper().quit();
              }
              mBackgroundHandler = null;
          }
      }
  }
  </code></pre>
  
  <p><strong>TLDR: What is the best way to implement ongoing background work in modern (8.1+) Android?</strong> It does appear, given the architecture of Worker/WorkManager, that this kind of ongoing background work is being snuffed out by Google. <strong>Is a wait()/notify() pattern in a Worker acceptable, or will this workaround get killed by the system?</strong></p>
  
  <p>Any tips would be appreciated.</p>
  
  <p>Edit: </p>
  
  <p>I was hoping to avoid using a foreground Service + ongoing notification. The answer <a href="https://stackoverflow.com/a/18281520/10281800">here</a> seemed promising, but it was apparently patched in Android 7.1. On my phone running Android 9, my wireless BLE headphones connect almost immediately when taken out of their case. The headphone vendor is NOT running a foreground service (at least not visibly -- there is no persistent notification) to detect the advertisement. I have no idea how they're doing this so reliably.</p>