0

I'm building an app to control led bulbs (Mi-Light, limitlessLed and the likes). The purpose of the app is to have effects that the user can select and let run for an indefinite amount of time. For example, a "candle light" effect might change the bulbs color between shades of yellow and red, more or less randomly, until the user decides to stop.

Quick background info: the way these bulbs are controlled is through UDP packages which are sent over the WiFi network. Consequently, I need the app to keep on sending such UDP packages even when the device is sleeping.

After researching a little, I ended up making use of a wakelock in order to let the device broadcast UDP packages through the WiFi network even when sleeping (please do tell me in case there is a better approach I didn't find out about).

Everything works fine for some minutes (maybe 10?), until the device seemingly goes into some sort of deep sleep mode and stops sending packages over the network.

How can I prevent this from happening? And more generally speaking, what is a good approach I should take in order to accomplish what described above?

Just for reference, here's a sample snippet of code which just rotates through an array of 7 colours:

[...]

  private static boolean animationRunning = false;
  private Timer timer = new Timer();
  private PowerManager mgr = (PowerManager)getReactApplicationContext().getSystemService(Context.POWER_SERVICE);
  private PowerManager.WakeLock wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");

[...]

public void startAnimation(final int step) {

animationRunning = true;
wakeLock.acquire();

final int[] steps = { 0, 66, 99, 122, 166, 199, 255 };

resetTimer();
timer.scheduleAtFixedRate(new TimerTask(){
  int curstep = 0;
  @Override
  public void run(){
    byte[] Bytes = {64, (byte)steps[curstep], 85};
    try {sendUdp(Bytes);} catch(IOException e) {};
    curstep = curstep + 1;

    if(curstep == steps.length) {
      curstep = 1;
    }
  }
},0,1000);
}
coconup
  • 945
  • 10
  • 17
  • 1
    You have added this code in Android Service right? – Arpit Ratan Jun 13 '16 at 16:47
  • 1
    Are you by any chance testing on a Xiaomi device? MIUI tends to kill background processes no matter what. – earthw0rmjim Jun 13 '16 at 16:48
  • Extending on what @ArpitRatan said, you need this in a service, and if you want it to run indefinitely in the background it will need a notification to go with it or Android will assume it can be gc'ed. – zgc7009 Jun 13 '16 at 16:49
  • Thanks for the comments! @ArpitRatan @zgc7009 I'm running this in a native module as part of an react native project: `public class LightControllerModule extends ReactContextBaseJavaModule {` As such, it is already separate from the UI thread and therefore not blocking. Is this the same as running it into a service or should I still create a separate service to be called from the module? Also, could you quickly give me a reference to how to add a notification? I've read alarms could be used to 're-wake' the device, not sure if you're referring to that. – coconup Jun 14 '16 at 08:05
  • @user13 nope I'm running this on a Galaxy S6 – coconup Jun 14 '16 at 08:06
  • I moved everything to a service just to give it a try, the behaviour is unfortunately the same: after 15 minutes, the packages just stop being sent. When I wake up the phone manually and re-navigate to the app, it's seemingly reinitiated (white screen for a couple of secs and then the main activity pops up). – coconup Jun 14 '16 at 17:13

2 Answers2

2

I ended up implementing a foreground service (startForeground()) as explained here

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

        _acquireWakelock();

        _showNotification();

        [do your thing]

        return START_REDELIVER_INTENT;
}

private void _showNotification() {
    Intent notificationIntent = new Intent(this, MainActivity.class);
    notificationIntent.setAction("com.lightcontrollerrn2.action.main");
    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
            | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            notificationIntent, 0);

    Bitmap icon = BitmapFactory.decodeResource(getResources(),
            R.mipmap.ic_launcher);

    Notification notification = new NotificationCompat.Builder(this)
            .setContentTitle("Light Controller")
            .setTicker("Light Controller")
            .setContentText("Light effects are running")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
            .setContentIntent(pendingIntent)
            .setOngoing(true)
            .build();

    startForeground(1337,
            notification);
}

A couple things I found out along the way which might help others as well:

  1. The foreground service still requires a wakelock in order to keep on running when the device is idle;

  2. An additional reason you might run into similar issues is the new Doze mode introduced in Android 6.0 (https://developer.android.com/training/monitoring-device-state/doze-standby.html). In my case though, it seems the service keeps on running long enough without the need for me to tackle the device going into doze mode (I tested with roughly one hour and I'm happy with it). EDIT: for the sake of completion, I tackled this one as well. Here's how you do it (within the Service itself):

    @Override
    public void onCreate() {
        super.onCreate();
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        String packageName = getPackageName();
        if (Build.VERSION.SDK_INT >= 23 && !pm.isIgnoringBatteryOptimizations(packageName)) {
            Intent intent = new Intent();
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + packageName));
            startActivity(intent);
        }
    }
    
Tim
  • 41,901
  • 18
  • 127
  • 145
coconup
  • 945
  • 10
  • 17
  • Right, a foreground service would be a better solution in Android. In iOS, however, faking a music player seems to be the only way to keep your app running in the background without being killed. Friendly reminder: I haven't seen the up vote yet :) – Dat Nguyen Jun 24 '16 at 11:08
0

There are two ways I can think of to accomplish your requirement.

  1. If you can keep your app in the foreground, you can prevent the device from going to sleep by adding android:keepScreenOn="true" to your activity (for more info, see https://developer.android.com/training/scheduling/wakelock.html).

  2. The second way is to fake that your app is playing music in the background, and hence doesn't get killed by the OS. You can just play an silent mp3 that is stored locally within your app, and repeat it for unlimited amount of time. The OS will never kill your app, until it runs out of battery :)

Hope it helps.

Dat Nguyen
  • 1,626
  • 22
  • 25