Skip to content

Commit 1a95a64

Browse files
committed
Schedule focus delay with service
* Focus delay was a postDelay that if the application ended before postDelay ran then on_focus schedule won't happen * Create OSFocusDelaySync and reuse OSBackgroundSync * Save unsent time and influences as fast as the user left the application * Create OSFocusTimeProcessorFactory to get proper processor from influences
1 parent 30d8a89 commit 1a95a64

18 files changed

+736
-400
lines changed

OneSignalSDK/onesignal/src/main/AndroidManifest.xml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,19 @@
8383
<service android:name="com.onesignal.FCMIntentJobService"
8484
android:permission="android.permission.BIND_JOB_SERVICE"/>
8585

86-
<service android:name="com.onesignal.SyncService" android:stopWithTask="true" />
87-
<service android:name="com.onesignal.SyncJobService"
88-
android:permission="android.permission.BIND_JOB_SERVICE" />
86+
<service
87+
android:name="com.onesignal.FocusDelaySyncService"
88+
android:stopWithTask="true" />
89+
<service
90+
android:name="com.onesignal.FocusDelaySyncJobService"
91+
android:permission="android.permission.BIND_JOB_SERVICE" />
92+
93+
<service
94+
android:name="com.onesignal.SyncService"
95+
android:stopWithTask="true" />
96+
<service
97+
android:name="com.onesignal.SyncJobService"
98+
android:permission="android.permission.BIND_JOB_SERVICE" />
8999

90100
<activity android:name="com.onesignal.PermissionsActivity"
91101
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

OneSignalSDK/onesignal/src/main/java/com/onesignal/ActivityLifecycleHandler.java

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,10 @@
2929

3030
import android.annotation.SuppressLint;
3131
import android.app.Activity;
32+
import android.content.Context;
3233
import android.content.pm.ActivityInfo;
3334
import android.content.res.Configuration;
3435
import android.os.Build;
35-
import android.os.Handler;
36-
import android.os.HandlerThread;
37-
import android.os.Looper;
3836
import android.view.ViewTreeObserver;
3937

4038
import androidx.annotation.NonNull;
@@ -47,10 +45,6 @@
4745

4846
class ActivityLifecycleHandler implements OSSystemConditionController.OSSystemConditionHandler {
4947

50-
public FocusHandlerThread getFocusHandlerThread() {
51-
return FocusHandlerThread.getFocusHandlerThread();
52-
}
53-
5448
abstract static class ActivityAvailableListener {
5549
void available(@NonNull Activity activity) {
5650
}
@@ -68,6 +62,7 @@ void lostFocus() {
6862
private static final Map<String, OSSystemConditionController.OSSystemConditionObserver> sSystemConditionObservers = new ConcurrentHashMap<>();
6963
private static final Map<String, KeyboardListener> sKeyboardListeners = new ConcurrentHashMap<>();
7064

65+
private static AppFocusRunnable appFocusRunnable;
7166
@SuppressLint("StaticFieldLeak")
7267
private Activity curActivity = null;
7368
private boolean nextResumeIsFirstActivity = false;
@@ -170,18 +165,24 @@ private void onOrientationChanged() {
170165
}
171166

172167
private void handleLostFocus() {
173-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Handling lost focus");
174-
FocusHandlerThread.getFocusHandlerThread().runRunnable(new AppFocusRunnable());
168+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "ActivityLifecycleHandler Handling lost focus");
169+
if (appFocusRunnable != null && appFocusRunnable.backgrounded && !appFocusRunnable.completed)
170+
return;
171+
172+
OneSignal.getFocusTimeController().appStopped();
173+
OSFocusDelaySync.getInstance().scheduleSyncTask(OneSignal.appContext);
175174
}
176175

177176
private void handleFocus() {
178-
if (FocusHandlerThread.getFocusHandlerThread().hasBackgrounded() || nextResumeIsFirstActivity) {
177+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "ActivityLifecycleHandler handleFocus, with runnable: " + appFocusRunnable + " nextResumeIsFirstActivity: " + nextResumeIsFirstActivity);
178+
if (hasBackgrounded() || nextResumeIsFirstActivity) {
179+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "ActivityLifecycleHandler reset background state, call app focus");
179180
nextResumeIsFirstActivity = false;
180-
FocusHandlerThread.getFocusHandlerThread().resetBackgroundState();
181+
resetBackgroundState();
181182
OneSignal.onAppFocus();
182183
} else {
183-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Cancel focus lost worker");
184-
FocusHandlerThread.getFocusHandlerThread().stopScheduledRunnable();
184+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "ActivityLifecycleHandler cancel background lost focus sync task");
185+
OSFocusDelaySync.getInstance().cancelBackgroundSyncTask(OneSignal.appContext);
185186
}
186187
}
187188

@@ -224,7 +225,6 @@ public Activity getCurActivity() {
224225
}
225226

226227
public void setCurActivity(Activity activity) {
227-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "setCurActivity activity: " + activity);
228228
curActivity = activity;
229229
for (Map.Entry<String, ActivityAvailableListener> entry : sActivityAvailableListeners.entrySet()) {
230230
entry.getValue().available(curActivity);
@@ -249,68 +249,47 @@ void setNextResumeIsFirstActivity(boolean nextResumeIsFirstActivity) {
249249
this.nextResumeIsFirstActivity = nextResumeIsFirstActivity;
250250
}
251251

252-
static class FocusHandlerThread extends HandlerThread {
253-
private static FocusHandlerThread timeoutHandler;
254-
private Handler mHandler;
255-
private AppFocusRunnable appFocusRunnable;
256-
257-
static FocusHandlerThread getFocusHandlerThread() {
258-
if (timeoutHandler == null) {
259-
synchronized (SYNC_LOCK) {
260-
if (timeoutHandler == null)
261-
timeoutHandler = new FocusHandlerThread();
262-
}
263-
}
264-
return timeoutHandler;
265-
}
266-
267-
private FocusHandlerThread() {
268-
super("FocusHandlerThread");
269-
start();
270-
mHandler = new Handler(getLooper());
271-
}
272-
273-
Looper getHandlerLooper() {
274-
return mHandler.getLooper();
275-
}
276-
277-
void resetBackgroundState() {
278-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "FocusHandlerThread resetBackgroundState");
279-
if (appFocusRunnable != null)
280-
appFocusRunnable.backgrounded = false;
281-
}
282-
283-
void stopScheduledRunnable() {
284-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "FocusHandlerThread stopScheduledRunnable");
285-
mHandler.removeCallbacksAndMessages(null);
286-
}
287-
288-
void runRunnable(AppFocusRunnable runnable) {
289-
if (appFocusRunnable != null && appFocusRunnable.backgrounded && !appFocusRunnable.completed)
290-
return;
252+
void resetBackgroundState() {
253+
if (appFocusRunnable != null)
254+
appFocusRunnable.backgrounded = false;
255+
}
291256

292-
appFocusRunnable = runnable;
293-
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Post delay on app lost focus method");
294-
mHandler.removeCallbacksAndMessages(null);
295-
mHandler.postDelayed(runnable, 2000);
296-
}
257+
boolean hasBackgrounded() {
258+
return appFocusRunnable != null && appFocusRunnable.backgrounded;
259+
}
297260

298-
boolean hasBackgrounded() {
299-
return appFocusRunnable != null && appFocusRunnable.backgrounded;
300-
}
261+
static void runLostFocusLogic(Context context) {
262+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "ActivityLifecycleHandler runLostFocusLogic");
263+
ActivityLifecycleHandler activityLifecycleHandler = ActivityLifecycleListener.getActivityLifecycleHandler();
264+
if (activityLifecycleHandler == null || activityLifecycleHandler.curActivity == null)
265+
OneSignal.setInForeground(false);
266+
appFocusRunnable = new AppFocusRunnable();
267+
OSFocusDelaySync.getInstance().doBackgroundSync(
268+
context,
269+
appFocusRunnable
270+
);
301271
}
302272

303-
private static class AppFocusRunnable implements Runnable {
273+
static class AppFocusRunnable implements Runnable {
304274
private boolean backgrounded, completed;
305275

306276
public void run() {
277+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "ActivityLifecycleHandler running AppFocusRunnable");
307278
backgrounded = true;
308279
for (Map.Entry<String, ActivityAvailableListener> entry : sActivityAvailableListeners.entrySet()) {
309280
entry.getValue().lostFocus();
310281
}
311282
OneSignal.onAppLostFocus();
312283
completed = true;
313284
}
285+
286+
@Override
287+
public String toString() {
288+
return "AppFocusRunnable{" +
289+
"backgrounded=" + backgrounded +
290+
", completed=" + completed +
291+
'}';
292+
}
314293
}
315294

316295
static class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Modified MIT License
3+
*
4+
* Copyright 2020 OneSignal
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* 1. The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* 2. All copies of substantial portions of the Software may only be used in connection
17+
* with services provided by OneSignal.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
package com.onesignal;
29+
30+
import android.app.job.JobParameters;
31+
import android.app.job.JobService;
32+
import android.os.Build;
33+
34+
import androidx.annotation.RequiresApi;
35+
36+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
37+
public class FocusDelaySyncJobService extends JobService {
38+
39+
@Override
40+
public boolean onStartJob(JobParameters jobParameters) {
41+
ActivityLifecycleHandler.runLostFocusLogic(this);
42+
return true;
43+
}
44+
45+
@Override
46+
public boolean onStopJob(JobParameters jobParameters) {
47+
boolean reschedule = OSFocusDelaySync.getInstance().stopSyncBgThread();
48+
OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "FocusDelaySyncJobService onStopJob called, system conditions not available reschedule: " + reschedule);
49+
return reschedule;
50+
}
51+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Modified MIT License
3+
*
4+
* Copyright 2020 OneSignal
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* 1. The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* 2. All copies of substantial portions of the Software may only be used in connection
17+
* with services provided by OneSignal.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
*/
27+
28+
package com.onesignal;
29+
30+
import android.app.Service;
31+
import android.content.Intent;
32+
import android.os.IBinder;
33+
34+
import androidx.annotation.Nullable;
35+
36+
public class FocusDelaySyncService extends Service {
37+
38+
@Override
39+
public int onStartCommand(Intent intent, int flags, int startId) {
40+
ActivityLifecycleHandler.runLostFocusLogic(this);
41+
return START_STICKY;
42+
}
43+
44+
// This Service does not support bindings
45+
@Nullable
46+
@Override
47+
public IBinder onBind(Intent intent) {
48+
return null;
49+
}
50+
51+
}

0 commit comments

Comments
 (0)