55import android .content .Intent ;
66import android .content .SharedPreferences ;
77import android .content .pm .ServiceInfo ;
8+ import android .location .Location ;
9+ import android .location .LocationManager ;
810import android .os .Build ;
911import android .os .IBinder ;
1012
1719import com .google .common .util .concurrent .Futures ;
1820import com .google .common .util .concurrent .ListenableFuture ;
1921import com .jackpf .locationhistory .client .config .ConfigRepository ;
22+ import com .jackpf .locationhistory .client .location .LocationData ;
23+ import com .jackpf .locationhistory .client .location .PassiveLocationListener ;
2024import com .jackpf .locationhistory .client .permissions .AppRequirement ;
2125import com .jackpf .locationhistory .client .permissions .AppRequirementsUtil ;
26+ import com .jackpf .locationhistory .client .permissions .PermissionsManager ;
2227import com .jackpf .locationhistory .client .ui .Notifications ;
2328import com .jackpf .locationhistory .client .util .Logger ;
2429import com .jackpf .locationhistory .client .worker .BeaconResult ;
2530import com .jackpf .locationhistory .client .worker .BeaconTask ;
2631import com .jackpf .locationhistory .client .worker .RetryableException ;
2732
33+ import java .io .IOException ;
2834import java .util .List ;
2935import java .util .concurrent .ExecutorService ;
3036import java .util .concurrent .Executors ;
3137import java .util .concurrent .TimeUnit ;
38+ import java .util .function .Consumer ;
3239
3340public class BeaconService extends Service {
3441 private static final Logger log = new Logger ("BeaconService" );
3542 private ExecutorService executorService ;
3643 private ConfigRepository configRepository ;
3744 private BeaconScheduler beaconScheduler ;
45+ @ Nullable
46+ private BeaconTask beaconTask ;
47+ private PassiveLocationListener passiveLocationListener ;
48+ private static final long PASSIVE_LISTENER_MIN_TIME_MS = TimeUnit .MINUTES .toMillis (1 );
49+ private static final float PASSIVE_LISTENER_MIN_DISTANCE_M = 0.0f ;
3850 private static final int PERSISTENT_NOTIFICATION_ID = 1 ;
3951 private static final String ACTION_RUN_TASK = "com.jackpf.locationhistory.client.ACTION_BEACON_SERVICE" ;
52+ private static final String ACTION_PASSIVE_LOCATION = "com.jackpf.locationhistory.client.ACTION_PASSIVE_LOCATION" ;
4053
41- private final Runnable beaconTask = () ->
54+ private final Runnable beaconTaskRunnable = () ->
4255 beaconScheduler .runWithWakeLock (() -> {
43- ListenableFuture <BeaconResult > beaconResult = BeaconTask
44- .runSafe (BeaconService .this , executorService );
56+ ListenableFuture <BeaconResult > beaconResult = beaconTask .run ();
4557
4658 Futures .addCallback (beaconResult , new FutureCallback <BeaconResult >() {
4759 @ Override
@@ -62,6 +74,10 @@ public void onFailure(@NonNull Throwable t) {
6274 return beaconResult ;
6375 });
6476
77+ private final Consumer <LocationData > passiveBeaconTaskRunnable = locationData -> {
78+ beaconScheduler .runWithWakeLock (() -> beaconTask .passiveRun (locationData ));
79+ };
80+
6581 private void scheduleNext (long delayMillis ) {
6682 beaconScheduler .scheduleNext (this , BeaconService .class , ACTION_RUN_TASK , delayMillis );
6783 }
@@ -78,8 +94,20 @@ private void scheduleNext(long delayMillis) {
7894 }
7995 };
8096
97+ private boolean hasRecentRun () {
98+ long millisSinceLastRun = System .currentTimeMillis () - configRepository .getLastRunTimestamp ();
99+ long updateIntervalMillis = configRepository .getUpdateIntervalMillis ();
100+ return millisSinceLastRun < updateIntervalMillis ;
101+ }
102+
81103 private long regularDelayMillis () {
82- return TimeUnit .MINUTES .toMillis (configRepository .getUpdateIntervalMinutes ());
104+ return configRepository .getUpdateIntervalMillis ();
105+ }
106+
107+ private long rescheduledDelayMillis () {
108+ long millisSinceLastRun = System .currentTimeMillis () - configRepository .getLastRunTimestamp ();
109+ long millisToNextRun = Math .max (configRepository .getUpdateIntervalMillis () - millisSinceLastRun , 0 );
110+ return Math .min (configRepository .getUpdateIntervalMillis (), millisToNextRun );
83111 }
84112
85113 private long retryDelayMillis () {
@@ -102,24 +130,59 @@ public void onCreate() {
102130 configRepository .registerOnSharedPreferenceChangeListener (configChangeListener );
103131
104132 beaconScheduler = BeaconScheduler .create (this , BeaconScheduler .DEFAULT_WAKELOCK_TIMEOUT );
133+ try {
134+ beaconTask = BeaconTask .create (this , executorService );
135+ } catch (IOException e ) {
136+ log .e ("Unable to create beacon task" , e );
137+ scheduleNext (retryDelayMillis ());
138+ return ;
139+ }
140+
141+ // Listen for passive location updates
142+ passiveLocationListener = new PassiveLocationListener (this , new PermissionsManager (this ),
143+ ACTION_PASSIVE_LOCATION , BeaconService .class );
144+ passiveLocationListener .startMonitoring (PASSIVE_LISTENER_MIN_TIME_MS , PASSIVE_LISTENER_MIN_DISTANCE_M );
145+ }
146+
147+ private void handleRunAction () {
148+ if (!hasRecentRun ()) {
149+ beaconTaskRunnable .run ();
150+ } else {
151+ log .d ("Re-scheduling due to recent run" );
152+ scheduleNext (rescheduledDelayMillis ());
153+ }
154+ }
155+
156+ private void handlePassiveLocationAction (Intent intent ) {
157+ if (intent .hasExtra (LocationManager .KEY_LOCATION_CHANGED )) {
158+ Location location = (Location ) intent .getExtras ().get (LocationManager .KEY_LOCATION_CHANGED );
159+ log .i ("Passive location received: %s" , location );
160+
161+ if (location != null ) {
162+ passiveBeaconTaskRunnable .accept (LocationData .passive (location ));
163+ }
164+ } else {
165+ log .w ("Passive location intent had no location data" );
166+ }
105167 }
106168
107169 @ Override
108170 public int onStartCommand (Intent intent , int flags , int startId ) {
109171 super .onStartCommand (intent , flags , startId );
110172
111- Notifications notifications = new Notifications (this );
112- ServiceCompat .startForeground (this ,
113- PERSISTENT_NOTIFICATION_ID ,
114- notifications .createPersistentNotification (
115- getString (R .string .persistent_notification_title ),
116- configRepository .inHighAccuracyMode () ? getString (R .string .persistent_notification_high_accuracy_mode )
117- : getString (R .string .persistent_notification_message )
118- ),
119- Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ? ServiceInfo .FOREGROUND_SERVICE_TYPE_LOCATION : 0 );
173+ // Can be null if init failed in onCreate
174+ if (beaconTask == null ) {
175+ log .e ("Empty beacon task" );
176+ stopSelf ();
177+ return START_NOT_STICKY ;
178+ }
179+
180+ createPersistentNotification ();
120181
121182 if (intent == null || ACTION_RUN_TASK .equals (intent .getAction ())) {
122- beaconTask .run ();
183+ handleRunAction ();
184+ } else if (ACTION_PASSIVE_LOCATION .equals (intent .getAction ())) {
185+ handlePassiveLocationAction (intent );
123186 }
124187
125188 return START_STICKY ;
@@ -132,6 +195,19 @@ public void onDestroy() {
132195 if (executorService != null ) executorService .shutdown ();
133196 if (configRepository != null )
134197 configRepository .unregisterOnSharedPreferenceChangeListener (configChangeListener );
198+ if (passiveLocationListener != null ) passiveLocationListener .stopMonitoring ();
199+ }
200+
201+ private void createPersistentNotification () {
202+ Notifications notifications = new Notifications (this );
203+ ServiceCompat .startForeground (this ,
204+ PERSISTENT_NOTIFICATION_ID ,
205+ notifications .createPersistentNotification (
206+ getString (R .string .persistent_notification_title ),
207+ configRepository .inHighAccuracyMode () ? getString (R .string .persistent_notification_high_accuracy_mode )
208+ : getString (R .string .persistent_notification_message )
209+ ),
210+ Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ? ServiceInfo .FOREGROUND_SERVICE_TYPE_LOCATION : 0 );
135211 }
136212
137213 public static void startForeground (Context context ) {
0 commit comments