[update] Removes the foreground service requirement for the timer service.

This commit is contained in:
Patrick Schneider 2025-08-03 14:04:32 +02:00
commit 5fb7ca4180
4 changed files with 110 additions and 17 deletions

View file

@ -10,8 +10,6 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application
android:name=".PFAktivpause"
@ -120,8 +118,7 @@
<service
android:name="org.secuso.aktivpause.service.TimerService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse"/>
android:exported="false" />
<service
android:name=".backup.PFABackupService"
android:enabled="true"

View file

@ -2,9 +2,11 @@ package org.secuso.aktivpause.activities;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
@ -12,6 +14,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.preference.PreferenceManager;
@ -117,14 +120,39 @@ public class ExerciseActivity extends AppCompatActivity implements LoaderManager
private SharedPreferences pref;
private Handler mHandler;
private TimerService timerService = null;
private boolean serviceBound = false;
/**
* Defines callbacks for service binding, passed to bindService()
* Performs an initial GUI update when connection is established.
**/
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
TimerService.TimerServiceBinder binder = (TimerService.TimerServiceBinder) service;
timerService = binder.getService();
serviceBound = true;
timerService.setIsAppInBackground(false);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
serviceBound = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise);
Intent stopTimer = new Intent(this, TimerService.class);
stopTimer.setAction(ACTION_STOP_TIMER);
startService(stopTimer);
bindService(stopTimer, serviceConnection, Context.BIND_AUTO_CREATE);
// stopTimer.setAction(ACTION_STOP_TIMER);
// startService(stopTimer);
mHandler = new Handler();
@ -327,6 +355,10 @@ public class ExerciseActivity extends AppCompatActivity implements LoaderManager
super.onResume();
isActivityVisible = true;
if (timerService != null) {
timerService.setIsAppInBackground(false);
}
if (isBreakFinished) {
showEndDialog(this);
}
@ -341,6 +373,10 @@ public class ExerciseActivity extends AppCompatActivity implements LoaderManager
super.onPause();
isActivityVisible = false;
if (timerService != null) {
timerService.setIsAppInBackground(true);
}
if(isBreakFinished) {
// TODO: Either start a short Timer to see if the user comes back - or start the next work time rand finish this activity
// TODO: for now we just finish
@ -350,6 +386,28 @@ public class ExerciseActivity extends AppCompatActivity implements LoaderManager
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
/**
* Stop the notification when activity is destroyed
*/
@Override
public void onDestroy() {
if (timerService != null) {
timerService.workoutClosed();
}
super.onDestroy();
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (serviceBound) {
unbindService(serviceConnection);
serviceBound = false;
}
}
@Override
public void onLoadFinished(Loader<ExerciseSet> loader, ExerciseSet set) {
if (set != null) {

View file

@ -11,6 +11,7 @@ import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.core.app.AlarmManagerCompat;
import androidx.legacy.content.WakefulBroadcastReceiver;
import org.secuso.aktivpause.service.TimerService;
@ -79,7 +80,7 @@ public class TimerSchedulerReceiver extends WakefulBroadcastReceiver {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent automaticTimerIntent = new Intent(context, TimerSchedulerReceiver.class);
PendingIntent automaticTimerPending = PendingIntent.getBroadcast(context, 0, automaticTimerIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent automaticTimerPending = PendingIntent.getBroadcast(context, 0, automaticTimerIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timestamp);
@ -143,12 +144,8 @@ public class TimerSchedulerReceiver extends WakefulBroadcastReceiver {
}
}
if(done || !scheduleExerciseDaysEnabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), automaticTimerPending);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), automaticTimerPending);
}
if((done || !scheduleExerciseDaysEnabled)) {
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), automaticTimerPending);
}
}

View file

@ -12,6 +12,7 @@ import android.content.SharedPreferences;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.Settings;
@ -56,6 +57,9 @@ public class TimerService extends Service {
private static final int UPDATE_INTERVAL = 125;
public static final int NOTIFICATION_ID = 31337;
private NotificationCompat.Builder notiBuilder = null;
private NotificationManager notiManager = null;
private boolean isAppInBackground = false;
private TimerServiceBinder mBinder = new TimerServiceBinder();
private CountDownTimer mTimer;
@ -366,13 +370,50 @@ public class TimerService extends Service {
}
private void updateNotification() {
if(isRunning() || isPaused()) {
ServiceCompat.startForeground(this, NOTIFICATION_ID, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
stopForeground(true);
if(isAppInBackground) {
Notification notification = buildNotification();
notiManager.notify(NOTIFICATION_ID, notification);
}
else if(notiManager != null) {
notiManager.cancel(NOTIFICATION_ID);
}
}
/**
* Check if the app is in the background.
* If so, start a notification showing the current timer.
*
* @param isInBackground Sets global flag to determine whether the app is in the background
*/
public void setIsAppInBackground(boolean isInBackground){
this.isAppInBackground = isInBackground;
//Execute after short delay to prevent short notification popup if workoutActivity is closed
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
updateNotification();
}
}, 700);
}
/**
* Cancel the notification when workout activity is destroyed
*/
public void workoutClosed(){
this.isAppInBackground = false;
notiManager.cancel(NOTIFICATION_ID);
}
// private void updateNotification() {
// if(isRunning() || isPaused()) {
// ServiceCompat.startForeground(this, NOTIFICATION_ID, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
// } else {
// stopForeground(true);
// }
// }
@Override
public IBinder onBind(Intent intent) {
return mBinder;