11package com .jackpf .locationhistory .client ;
22
33import android .app .Service ;
4+ import android .content .Context ;
45import android .content .Intent ;
56import android .content .SharedPreferences ;
67import android .content .pm .ServiceInfo ;
78import android .os .Build ;
8- import android .os .Handler ;
9- import android .os .HandlerThread ;
109import android .os .IBinder ;
1110
1211import androidx .annotation .NonNull ;
1817import com .google .common .util .concurrent .Futures ;
1918import com .google .common .util .concurrent .ListenableFuture ;
2019import com .jackpf .locationhistory .client .config .ConfigRepository ;
20+ import com .jackpf .locationhistory .client .permissions .AppRequirementsUtil ;
2121import com .jackpf .locationhistory .client .ui .Notifications ;
2222import com .jackpf .locationhistory .client .util .Logger ;
2323import com .jackpf .locationhistory .client .worker .BeaconResult ;
2929import java .util .concurrent .TimeUnit ;
3030
3131public class BeaconService extends Service {
32- private final Logger log = new Logger (this );
33- private HandlerThread handlerThread ;
34- private Handler handler ;
32+ private static final Logger log = new Logger ("BeaconService" );
3533 private ExecutorService executorService ;
3634 private ConfigRepository configRepository ;
35+ private BeaconScheduler beaconScheduler ;
3736 private static final int PERSISTENT_NOTIFICATION_ID = 1 ;
37+ private static final String ACTION_RUN_TASK = "com.jackpf.locationhistory.client.ACTION_BEACON_SERVICE" ;
38+ private static final long WAKELOCK_TIMEOUT = TimeUnit .MINUTES .toMillis (10 );
3839
3940 private static final String START_MESSAGE = "Beacon task started" ;
4041 private static final String SUCCESS_MESSAGE = "Beacon task completed successfully" ;
4142 private static final String FAILED_MESSAGE = "Beacon task failed" ;
4243 private static final String RETRY_MESSAGE = "Beacon task failed with retry" ;
4344
44- private final Runnable beaconTaskRunner = () -> {
45- log .i (START_MESSAGE );
46- log .appendEventToFile (BeaconService .this , START_MESSAGE );
47-
48- ListenableFuture <BeaconResult > beaconResult = BeaconTask
49- .runSafe (BeaconService .this , executorService );
50-
51- Futures .addCallback (beaconResult , new FutureCallback <BeaconResult >() {
52- @ Override
53- public void onSuccess (BeaconResult result ) {
54- log .i ("%s: %s" , SUCCESS_MESSAGE , result );
55- log .appendEventToFile (BeaconService .this , "%s: %s" , SUCCESS_MESSAGE , result );
56- scheduleNext (regularDelayMillis ());
57- }
58-
59- @ Override
60- public void onFailure (@ NonNull Throwable t ) {
61- if (t instanceof RetryableException ) {
62- log .w (RETRY_MESSAGE , t );
63- log .appendEventToFile (BeaconService .this , "%s: %s" , RETRY_MESSAGE , t .getMessage ());
64- scheduleNext (retryDelayMillis ());
65- } else {
66- log .e (FAILED_MESSAGE , t );
67- log .appendEventToFile (BeaconService .this , "%s: %s" , FAILED_MESSAGE , t .getMessage ());
68- scheduleNext (regularDelayMillis ());
69- }
70- }
71- }, ContextCompat .getMainExecutor (BeaconService .this ));
72- };
45+ private final Runnable beaconTask = () ->
46+ beaconScheduler .runWithWakeLock (() -> {
47+ log .i (START_MESSAGE );
48+ log .appendEventToFile (BeaconService .this , START_MESSAGE );
49+
50+ ListenableFuture <BeaconResult > beaconResult = BeaconTask
51+ .runSafe (BeaconService .this , executorService );
52+
53+ Futures .addCallback (beaconResult , new FutureCallback <BeaconResult >() {
54+ @ Override
55+ public void onSuccess (BeaconResult result ) {
56+ log .i ("%s: %s" , SUCCESS_MESSAGE , result );
57+ log .appendEventToFile (BeaconService .this , "%s: %s" , SUCCESS_MESSAGE , result );
58+ scheduleNext (regularDelayMillis ());
59+ }
60+
61+ @ Override
62+ public void onFailure (@ NonNull Throwable t ) {
63+ if (t instanceof RetryableException ) {
64+ log .w (RETRY_MESSAGE , t );
65+ log .appendEventToFile (BeaconService .this , "%s: %s" , RETRY_MESSAGE , t .getMessage ());
66+ scheduleNext (retryDelayMillis ());
67+ } else {
68+ log .e (FAILED_MESSAGE , t );
69+ log .appendEventToFile (BeaconService .this , "%s: %s" , FAILED_MESSAGE , t .getMessage ());
70+ scheduleNext (regularDelayMillis ());
71+ }
72+ }
73+ }, ContextCompat .getMainExecutor (BeaconService .this ));
74+
75+ return beaconResult ;
76+ });
77+
78+ private void scheduleNext (long delayMillis ) {
79+ beaconScheduler .scheduleNext (this , BeaconService .class , ACTION_RUN_TASK , delayMillis );
80+ }
7381
7482 private final SharedPreferences .OnSharedPreferenceChangeListener configChangeListener = (prefs , key ) -> {
7583 if (key != null &&
@@ -81,13 +89,6 @@ public void onFailure(@NonNull Throwable t) {
8189 }
8290 };
8391
84- private void scheduleNext (long delayMillis ) {
85- log .d ("Scheduling next beacon task in %dms" , delayMillis );
86-
87- handler .removeCallbacks (beaconTaskRunner );
88- handler .postDelayed (beaconTaskRunner , delayMillis );
89- }
90-
9192 private long regularDelayMillis () {
9293 return TimeUnit .MINUTES .toMillis (configRepository .getUpdateIntervalMinutes ());
9394 }
@@ -106,28 +107,27 @@ public IBinder onBind(Intent intent) {
106107 public void onCreate () {
107108 super .onCreate ();
108109
109- handlerThread = new HandlerThread ("ServiceTimer" );
110- handlerThread .start ();
111- handler = new Handler (handlerThread .getLooper ());
112110 executorService = Executors .newSingleThreadExecutor ();
113111
114112 configRepository = new ConfigRepository (this );
115113 configRepository .registerOnSharedPreferenceChangeListener (configChangeListener );
114+
115+ beaconScheduler = BeaconScheduler .create (this , WAKELOCK_TIMEOUT );
116116 }
117117
118118 @ Override
119119 public int onStartCommand (Intent intent , int flags , int startId ) {
120120 super .onStartCommand (intent , flags , startId );
121121
122122 Notifications notifications = new Notifications (this );
123-
124123 ServiceCompat .startForeground (this ,
125124 PERSISTENT_NOTIFICATION_ID ,
126125 notifications .createPersistentNotification (getString (R .string .persistent_notification_title ), getString (R .string .persistent_notification_message )),
127126 Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ? ServiceInfo .FOREGROUND_SERVICE_TYPE_LOCATION : 0 );
128127
129- handler .removeCallbacks (beaconTaskRunner );
130- handler .post (beaconTaskRunner );
128+ if (intent == null || ACTION_RUN_TASK .equals (intent .getAction ())) {
129+ beaconTask .run ();
130+ }
131131
132132 return START_STICKY ;
133133 }
@@ -136,10 +136,24 @@ public int onStartCommand(Intent intent, int flags, int startId) {
136136 public void onDestroy () {
137137 super .onDestroy ();
138138
139- if (handler != null ) handler .removeCallbacks (beaconTaskRunner );
140- if (handlerThread != null ) handlerThread .quitSafely ();
141139 if (executorService != null ) executorService .shutdown ();
142140 if (configRepository != null )
143141 configRepository .unregisterOnSharedPreferenceChangeListener (configChangeListener );
144142 }
143+
144+ public static void startForeground (Context context ) {
145+ log .d ("Starting service" );
146+ Intent intent = new Intent (context , BeaconService .class );
147+ intent .setAction (ACTION_RUN_TASK );
148+ ContextCompat .startForegroundService (context , intent );
149+ }
150+
151+ public static void startForegroundIfPermissionsGranted (Context context ) {
152+ if (AppRequirementsUtil .allGranted (context , AppRequirements .getRequirements (context ))) {
153+ log .d ("Permissions granted" );
154+ startForeground (context );
155+ } else {
156+ log .w ("Permissions not yet granted" );
157+ }
158+ }
145159}
0 commit comments