How to get sensor and gnss data on Android with screen off?

Viewed 80

I am developing an app that I want to use to collect Sensor/GNSS data for a database and future data science applications. I want to collect data for a longer period of time (at least an hour) and I want to include different situations like having the phone in the pocket or the backpack. Thus I need to be able to let the app run in the background with screen off.

I am using the following phones: Galaxy S20, Pixel 2 and 4 and POCOFONE 2 running either Android 10 or 11.

I have implemented a service, disabled battery optimization, tried to use a Wakelock and tried using a workaround that includes a Broadcastreceiver which reregisters sensors on ACTION_SCREEN_OFF. All this is good for about five minutes than the data stops coming. Once I turn the screen back on the data is coming again.

Any ideas are much appreciated thank you in advance.

Here is my main activity code:

@RequiresApi(api = Build.VERSION_CODES.R)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Spinner mFrequencySpinner = findViewById(R.id.frequency_spinner);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.frequency_array, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mFrequencySpinner.setAdapter(adapter);
        mFrequencySpinner.setOnItemSelectedListener(this);

        //------------------------------------------
        // TODO: export to an own fragment later
        mLabelGrid = findViewById(R.id.labels_grid);
        imageModelList = populateList();
        labelAdapter = new LabelAdapter(getApplicationContext(), imageModelList);
        mLabelGrid.setAdapter(labelAdapter);
        mLabelGrid.setOnItemClickListener((parent, view, position, id) -> setMeanOfTransport(view, position));
        //------------------------------------------

    }

    @Override
    protected void onStart() {
        super.onStart();

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) !=
                PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_CODE);
        }

        final boolean gpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

        if (!gpsEnabled) {
            DialogFragment dialog = new EnableLocationDialogFragment();
            dialog.show(getSupportFragmentManager(), "EnableLocationDialogFragment");
        }
    }

    public void startBackgroundService(View view) {

        appStartTime = System.currentTimeMillis();
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DLR_KN_MOBILITY_APP:WAKE_LOCK");
        mWakeLock.acquire(15*60*10000L /*150 minutes*/);
        Button btn = (Button) view;

        if ( !backgroundLogging ) {
            backgroundLogging = true;
            btn.setText(getResources().getString(R.string.stop_service));
            toggleBackgroundService(true);
        } else {
            btn.setText(getResources().getString(R.string.start_service));
            toggleBackgroundService(false);
            backgroundLogging = false;
            mWakeLock.release();
        }

    }

    private void toggleBackgroundService(boolean on) {

        if (!backgroundLogging) return;

        if (on) {
            Intent serviceIntent = new Intent(this, DataCollectorService.class);
            serviceIntent.putExtra("startTime", appStartTime);
            serviceIntent.putExtra("samplingFrequency", samplingFrequency);
            serviceIntent.putExtra("meanOfTrans", meanOfTrans);
            startService(serviceIntent);
        } else {
            stopService(new Intent(this, DataCollectorService.class));
        }

    }

And this is my Service code:

    @RequiresApi(api = Build.VERSION_CODES.R)
    @Override
    public void onCreate() {
        super.onCreate();

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        mLogger = new MyLogger(this);

        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
        mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

    }

    @RequiresApi(api = Build.VERSION_CODES.R)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (initial) {
            appStartTime = intent.getLongExtra("startTime", 0);
            samplingFrequency = intent.getIntExtra("samplingFrequency", 10000);
            registerSensors();
            initial = false;
        }
        meanOfTrans = intent.getIntExtra("meanOfTrans", 0);

        return Service.START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // We don't provide binding, so return null
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
        disableSensors();
    }

    @RequiresApi(api = Build.VERSION_CODES.R)
    private void registerSensors() {

        if (mAccelerometer != null) {
            mSensorManager.registerListener(this, mAccelerometer, samplingFrequency);
        }
        if (mGyroscope != null) {
            mSensorManager.registerListener(this, mGyroscope, samplingFrequency);
        }
        if (mMagnetometer != null) {
            mSensorManager.registerListener(this, mMagnetometer, samplingFrequency);
        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
                PackageManager.PERMISSION_GRANTED) {
            mLocationManager.registerGnssMeasurementsCallback(gnssMeasurementsEventListener);
            mLocationManager.addNmeaListener(nmeaMessageListener);
        }


    }

    private void disableSensors() {
        mSensorManager.unregisterListener(this);
        mLocationManager.unregisterGnssMeasurementsCallback(gnssMeasurementsEventListener);
        mLocationManager.removeNmeaListener(nmeaMessageListener);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        double time = event.timestamp*1E-9;
        float seconds = ( (float) (System.currentTimeMillis() - appStartTime) ) / 1000;
        int sensorType = event.sensor.getType();
        String logOut= "";
        String sens = "";

        switch (sensorType) {
            case Sensor.TYPE_ACCELEROMETER:
                sens = "ACC";
                break;
            case Sensor.TYPE_GYROSCOPE:
                sens = "GYR";
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                sens = "MAG";
                break;
        }

        logOut += getResources().getString(R.string.sample_format, sens, seconds, time, meanOfTrans, isGtp, event.values[0], event.values[1], event.values[2], event.accuracy);
        isGtp = false;
        mLogger.logSensor(logOut);

    }

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

    private final OnNmeaMessageListener nmeaMessageListener = new OnNmeaMessageListener() {
        @Override
        public void onNmeaMessage(String message, long timestamp) {
            mLogger.onNmeaReceived(timestamp, message);
        }
    };

    private final GnssMeasurementsEvent.Callback gnssMeasurementsEventListener =
            new GnssMeasurementsEvent.Callback() {
                @Override
                public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
                    GnssClock gnssClock = event.getClock();
                    for (GnssMeasurement measurement : event.getMeasurements()) {
                        mLogger.writeGnssMeasurementToFile(gnssClock, measurement);
                    }
                }

                @Override
                public void onStatusChanged(int status) {}
            };
}
0 Answers
Related