1

I am trying to develop a simple app which will record the user's activity (accelerometer values) on a txt or csv file.

My app consists of 2 java classes MainActivity and MyService. The MainActivity includes two buttons to start and stop the service and the required permissions. However, the onSensorChanged normally logs for the first 3 minutes after locking the phone (turning off the screen) and then stops logging. As soon as I open the screen the logd starts working again. Same behavior for the records in txt file. I found out that the app seems to be working excellent if I override the battery optimizations. However, I need the phone to also be working in doze mode to save some battery drain. Has anyone else had a similar issue?

Here is my Foreground Service:

public class MyService extends Service implements SensorEventListener {

    public static final String CHANNEL_ID = "ForegroundServiceChannel";
    private static final String TAG = "MyService";

    private Messenger messageHandler;


    private SensorManager mSensorManager;
    private Sensor mAccelerometer;
    private Context mContext;
    private PowerManager.WakeLock wakeLock = null;

    //private HandlerThread mSensorThread;
    //private Handler mHandler;

    @SuppressLint("MissingPermission")
    @Override
    public void onCreate() {
        super.onCreate();
        Log.v("shake service startup", "registering for shake");
        mContext = getApplicationContext();

        //mHandler = new Handler(mSensorThread.getLooper());

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager
                .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mSensorManager.registerListener(this, mAccelerometer,
                SensorManager.SENSOR_DELAY_NORMAL);


        PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Wakelock :: TAG");

        // Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
        // code be called whenever the phone enters standby mode.
        //IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        //registerReceiver(mReceiver, filter);
    }


    /*
    // BroadcastReceiver for handling ACTION_SCREEN_OFF.
    public BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            // Check action just to be on the safe side.
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                Log.v("shake mediator screen off","trying re-registration");
                // Unregisters the listener and registers it again.
                mSensorManager.unregisterListener(MyService.this);
                mSensorManager.registerListener(MyService.this, mAccelerometer,
                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
            }
        }
    };
    */

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String input = intent.getStringExtra("inputExtra");
        createNotificationChannel();
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
                0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Foreground Service")
                .setContentText(input)
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();

        startForeground(1, notification);

        return START_STICKY;

        //stopSelf();

        //return START_NOT_STICKY;
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel serviceChannel = new NotificationChannel(
                    CHANNEL_ID,
                    "Foreground Service Channel",
                    NotificationManager.IMPORTANCE_DEFAULT
            );

            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(serviceChannel);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(mSensorManager != null){
            //noinspection MissingPermission
            mSensorManager.unregisterListener(MyService.this);
        }
        //unregisterReceiver(mReceiver);
        try{
            wakeLock.release();//always release before acquiring for safety just in case
        }
        catch(Exception e){
            //probably already released
            Log.e(TAG, e.getMessage());
        }

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }



    @Override
    public void onSensorChanged(SensorEvent event) {
        Log.d(TAG, "onSensorChanged: " + event.timestamp + " " + event.values[0] + " " + event.values[1] + " " + event.values[2]);
        recordAccelValues(String.valueOf(event.timestamp),  event.values[0] + " " + event.values[1] + " " + event.values[2]);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    private void recordAccelValues(String time, String accel_values) {
        String record = time + " " + accel_values  + "\n";

        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            if (Build.VERSION.SDK_INT >= 23) {
                File sdcard = Environment.getExternalStorageDirectory();
                File dir = new File(sdcard.getAbsolutePath() + "/text/");
                if(!dir.exists()) {
                    dir.mkdir();
                }
                File file = new File(dir, "dailyRecordsAccel.dat");
                FileOutputStream os = null;
                try {
                    os = new FileOutputStream(file, true);
                    os.write(record.getBytes());
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }


}

As you can see in the code I tried several recommendations from other questions I found, like wakelock and Intent.ACTION_SCREEN_OFF but they didn't seem to work. Accelerometer stops delivering samples when the screen is off on Droid/Nexus One even with a WakeLock

PedroAGSantos
  • 2,336
  • 2
  • 17
  • 34
aristyan
  • 11
  • 3

2 Answers2

0

The only one way to keep alive your service it's to avoid battery optimization for your application. Which is possible within two ways below. Please note! In both cases you will keep device alive, which means that device will never sleep (enter doze states obviously). It's whole point of device sleep, to avoid pending work of background services like yours.

  • Using Android WakeLocks, For ex. below.

   val wakeLock: PowerManager.WakeLock =
           (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
               newWakeLock(PowerManager. FULL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
                acquire()
            }
        }
  • Changing setting to avoid battery optimization for specific app. As you mentioned in your question.
GensaGames
  • 5,538
  • 4
  • 24
  • 53
  • First of all thanks for the quick response. Well I have made an exact same service for GPS values and it seems to be working fine even when the phone is locked, that's why it seems weird to me that it stops recording accelerometer values after 3 minutes. Aren't foreground services supposed to be running even if the android is locked? Anyway is there any other possible way apart from using a service to accomplish what I want in doze mode too? – aristyan Oct 29 '19 at 20:21
  • To support work with normal device sleep activity you need to manage background work with Android API for it. For background purpose they created `JobSchedular` or `WorkManager`. But note, they would call you service implementation once during some timeline. For ex. once in 15 minutes (depending on the depth of the sleep). – GensaGames Oct 29 '19 at 20:24
0

It is normal behavior. Android delete all proceses to save power. If you want do a job then ask user to keep screen on, else you can use AlarmManager only to call a Service (Intent, Reciver) do "small job" and go to sleep again.

Style-7
  • 985
  • 12
  • 27
  • Thank you for answering. Like mentioned above, I would like to have continuous accelerometer recordings in case the android is moved by the user, otherwise there would be a useless battery drain. For example, if the user is sleeping I wouldn't like to have recordings of 7-8 hours since they are not giving me important information. I don't want to keep screen on cause this is another cause of battery drain. I have also tried AlarmManager (with an alarm each 30 secs) and WakefulBroadcastReceiver but they didn't seem to work, since I don't need a "small job". Any other suggestions? – aristyan Oct 29 '19 at 20:44
  • I read this book. it has good similar example (calculating distance by GPS ) https://www.ebooks.com/en-ua/95849637/head-first-android-development/griffiths-david-griffiths-dawn – Style-7 Oct 29 '19 at 21:03