2

Good evening,

I've been trying to achieve something for several days now and I literally don't know what else to try, I've basically tried everything I found online and it still doesn't work but I have the feeling that once I find the solution it must be something quite simple.

I'm working on a bigger project, but here I'm just trying to get a very simple example to work which I can later adapt to my project.

What I want to do

I want to have a counter add +1 every second for 1200 seconds (20 minutes) and write to a file every time it counts. I should end up with a file with 1200 lines with a timestamp per sample.

How I'm trying to do it

I've tried a million things, but here, I've just gone back to a very basic example so I can showcase it and ask for help:

  • I have a MAIN acticity with a single START button.
  • When clicking the button I start a new process from the activity. This is a FOREGROUND process.
  • I start counting in a loop. When I reach 1200, the counting stops.

My problem

While the phone screen is ON everything works just fine, but as soon as I lock the screen and put my phone in my pocket, depending on the phone I'm testing on, it starts to fail.

What I would like

If someone could tell me what to modify / add / change or even do it for me and write back here (it's a really simple project) I would be extremely grateful, I'm wasting a ton of time and I just can't seem to hit the right key.

Code

Since it's a very simple project I will just copy the 4 parts it consists of right here:

XML activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/start_button"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:layout_marginStart="52dp"
        android:layout_marginBottom="116dp"
        android:onClick="startProcess"
        android:text="@string/button_label_start"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/counter_textView"
        android:layout_width="383dp"
        android:layout_height="412dp"
        android:gravity="center_horizontal|center_vertical"
        android:text="@string/counter_label_value"
        android:textSize="160sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.428"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.169" />

</androidx.constraintlayout.widget.ConstraintLayout>

MANIFEST AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.helloworld">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <service
            android:name=".ForegroundService"
            android:enabled="true"
            android:exported="true"
            android:process=":externalProcess">
        </service>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MAIN MainActivity.java

package com.example.helloworld;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    public static final String CHANNEL_ID = "ForegroundServiceChannel";
    private TextView mShowCount;

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        int mCount = data.getIntExtra(ForegroundService.FOREGROUND_MESSAGE, -1);
        if(mShowCount != null)
            mShowCount.setText(Integer.toString(mCount));
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mShowCount = findViewById(R.id.counter_textView);
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

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

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    public void startProcess(View view) {
        PendingIntent pendingResult = createPendingResult(100, new Intent(), 0);
        Intent serviceIntent = new Intent(this, ForegroundService.class);
        serviceIntent.putExtra("pendingIntent", pendingResult);
        ContextCompat.startForegroundService(this, serviceIntent);
    }

    public void stopProcess(View view) {
        Intent serviceIntent = new Intent(this, ForegroundService.class);
        stopService(serviceIntent);
    }
}

FOREGROUND SERVICE ForegroundService.java

package com.example.helloworld;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import androidx.core.app.NotificationCompat;

import java.io.File;
import java.io.FileWriter;
import java.util.Date;

public class ForegroundService extends Service {

    public static final String CHANNEL_ID = "ForegroundServiceChannel";
    public static final String FOREGROUND_MESSAGE = "com.example.helloworld.FOREGROUND_MESSAGE";

    private PendingIntent data;
    private int mCount;
    private File basePath;

    public ForegroundService() {
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public boolean stopService(Intent name) {
        return super.stopService(name);
    }

    @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, MainActivity.CHANNEL_ID)
                .setContentTitle("Foreground Service")
                .setContentText(input)
                .setContentIntent(pendingIntent)
                .build();

        startForeground(1, notification);
        data = intent.getParcelableExtra("pendingIntent");
        this.basePath = this.getExternalFilesDir("recs");

        mCount = 0;
        new Thread(new Runnable() {
            public void run() {
                try {
                    while(mCount < 1200) {
                        Intent resultIntent = new Intent();
                        resultIntent.putExtra(FOREGROUND_MESSAGE, ++mCount);
                        writeFile((new Date().getTime() / 1000) + " Increasing counter: " + mCount + "\n");
                        data.send(ForegroundService.this, 200, resultIntent);
                        SystemClock.sleep(1000);
                    }
                }catch (Exception ignored){}
            }
        }).start();

        //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();
    }

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

    private void writeFile(String data)
    {
        File file = new File (basePath,"test");
        if (!file.exists()) {
            boolean mkdirs = file.mkdirs();
            if (!mkdirs) {
                Log.e("RECORDING", "Error creating SAVE BASE PATH");
            }
        }

        try{
            File counter_file = new File(file, "counter.txt");
            FileWriter writer = new FileWriter(counter_file, true);
            writer.append(data);
            writer.flush();
            writer.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

I know I might be asking a bit too much but I'm quite desperate at this point. I'd be really greatful if someone could come up with a working solution.

Thank you very much.

Louen
  • 27
  • 5
  • If the phone is off, doesn't a background service make more sense? What's your use case for counting when the phone is off, ooi? – Blundell Feb 13 '20 at 15:41
  • For functionality on recent Android versions use a ForegroundService (like you do) and (manually) whitelist the app in power save settings i.e. "no optimization". – Markus Kauppinen Feb 13 '20 at 15:48
  • @Blundell From what I've read (I've read so many different things I don't know what's what any more, lol) unless you implement it as a Foreground Service, when the phone goes to sleep, the process will just shut down (however, even as a foreground service, it's still happening). The use case is... much more complicated, but if I get this silly example to work, I'm confident I can get the whole solution to work too. – Louen Feb 13 '20 at 16:08
  • @MarkusKauppinen I've tried whitelisting the app with "no optimization" but it still doesn't work. Also in more complex examples... it just depended on the phone, the best results I got so far were in an Android One phone (clean Android) others I tried like Huawei (EMUI) or Xiaomi (MIUI) worked even worse (in all of them I turned optimization off and still no luck). – Louen Feb 13 '20 at 16:11
  • Sorry, haven't done this myself in a while. Like you say it does keep changing the rules! :-) My best advice would be to find a recent music playing example, perhaps this: https://developer.android.com/guide/topics/media/mediaplayer#mpandservices or this: https://developer.android.com/guide/topics/media-apps/audio-app/building-an-audio-app.html and just replace the music playing with number counting :-) – Blundell Feb 13 '20 at 16:14
  • 1
    @Blundell I actually tried something similar to that, I used this examples: https://github.com/android/location-samples . I used the "LocationUpdatesForegroundService" example. Modified it to write to a file so I could check if it worked properly. I compiled it.. tested it... and when the phone is "idle" it also stops working properly. I can't even rely on the official examples :(. I'm going crazy haha. – Louen Feb 13 '20 at 16:21
  • Yeah each manufacturer is now doing some ruthless things to the system to get the most from battery performance. I assume you've sanity checked on an emulator first? – Blundell Feb 13 '20 at 17:01
  • @Blundell Not sure what you mean by "sanity check" on an emulator, but no, I haven't been working with one. I'm direclty testing the example(s) on 3 different devices, I might get it to work on an emulator, but if it later fails on the actual device, it won't be much help :(. – Louen Feb 13 '20 at 17:23
  • I don't know why no-one's mentioned a "`WakeLock`" yet. You need a `WakeLock`. – greeble31 Feb 13 '20 at 17:43
  • @Louen well an emulator is as close to stock android as you can get, so "check your sanity" that it works on stock android, before checking if it works on different manufacturers versions of Android – Blundell Feb 13 '20 at 19:28
  • @Blundell oh ok, understood :). Actually one of my test devices runs Android One, which I believe would be the closes possible thing to the emulator (it's pretty much stock android) so I guess, I've been kind of "sanity checking" in my own way :P – Louen Feb 14 '20 at 10:33
  • @greeble31 I've tried using a WakeLock too (Partial wakelock especifically) but either I'm doing something wrong, or it doesn't work either. I know I'm asking a bit too much, but checkin the above code, could you tell me where / how to implement it? since my attempts failed miserably... – Louen Feb 14 '20 at 10:35
  • The Android One phone could actually be the worst contender for having a ruthless system that kills all background work to keep the under-powered phone working. :-) it's all just a guess tbh, I bet you are just missing something small in your code. Start a clean project and try again to reproduce the minimal code that will follow the example. – Blundell Feb 14 '20 at 10:36
  • @Louen You could just gain the `WakeLock` in `Service.onCreate()` and clean it up in `Service.onDestroy()`. If you'd like to post that edit, I would be happy to review it. By the way, I hope the service starts working the way you expect after you turn the screen back on. If your phone is really killing the foreground service, then that's a much more serious matter. – greeble31 Feb 14 '20 at 14:25
  • @greeble31 When turning the screen on, things start rolling again, it's just when it's off. I tried starting and finishing the wakelock in several different places, but didn't try there... let me get to it and I'll update. Hope it works :S. – Louen Feb 17 '20 at 18:01
  • @greeble31 just tried it out: on an Android One phone it seems to be working (I also got some other tests to work on this phone) however, I'm trying it on a Huawei phone with EMUI and it still doesn't work, about 15 seconds after turning the screen off, it stops counting until you turn it back on. – Louen Feb 17 '20 at 18:17
  • Check out some of the techniques on [this page](https://dontkillmyapp.com/huawei) – greeble31 Feb 17 '20 at 18:54

1 Answers1

3

You should try using WakeLock. From the official android documentation

This will prevent from letting the CPU going to sleep until a particular task is done.

In the onCreate() method of your service

@Override
public void onCreate() {
    super.onCreate();
    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
    WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
            "MyApp::MyWakelockTag");
    wakeLock.acquire();
    /*Rest of the
      code goes here*/
}

Don't forget to release your wakelock when your purpose ends, or else you might end up draining your users battery pretty fast.

@Override
public void onDestroy()
 {
   super.onDestroy();
   wakeLock.release();
 }
Vishist Varugeese
  • 1,500
  • 1
  • 17
  • 30
Acedev003
  • 51
  • 1
  • 7