Skip to content

Commit 3734cac

Browse files
committed
Fixed NPE with getRootWindowInsets
* Fixed null getRootWindowInsets by ensuring it available before setting up an IAM * This fixes a crash that could sometimes happen when tapping on a preview notification when the app is out of focus. * Also some other misc IAM clean up.
1 parent 1f34f57 commit 3734cac

File tree

5 files changed

+95
-82
lines changed

5 files changed

+95
-82
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,9 @@ static void setSystemConditionObserver(String key, OSSystemConditionController.O
7272
}
7373

7474
static void setActivityAvailableListener(String key, ActivityAvailableListener activityAvailableListener) {
75-
if (curActivity != null) {
76-
activityAvailableListener.available(curActivity);
77-
}
7875
sActivityAvailableListeners.put(key, activityAvailableListener);
76+
if (curActivity != null)
77+
activityAvailableListener.available(curActivity);
7978
}
8079

8180
static void removeSystemConditionObserver(String key) {

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

Lines changed: 54 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import android.content.Context;
88
import android.graphics.Color;
99
import android.graphics.drawable.ColorDrawable;
10-
import android.os.Build;
1110
import android.os.Handler;
1211
import android.support.annotation.NonNull;
1312
import android.support.annotation.Nullable;
@@ -16,16 +15,13 @@
1615
import android.view.Gravity;
1716
import android.view.View;
1817
import android.view.ViewGroup;
19-
import android.view.Window;
2018
import android.view.WindowManager;
2119
import android.view.animation.Animation;
2220
import android.webkit.WebView;
2321
import android.widget.LinearLayout;
2422
import android.widget.PopupWindow;
2523
import android.widget.RelativeLayout;
2624

27-
import java.lang.ref.WeakReference;
28-
2925
import static com.onesignal.OSViewUtils.dpToPx;
3026

3127
/**
@@ -76,14 +72,14 @@ interface InAppMessageViewListener {
7672
private RelativeLayout parentRelativeLayout;
7773
private DraggableRelativeLayout draggableRelativeLayout;
7874
private InAppMessageViewListener messageController;
79-
private Runnable dismissSchedule;
75+
private Runnable scheduleDismissRunnable;
8076

8177
InAppMessageView(@NonNull WebView webView, @NonNull WebViewManager.Position displayLocation, int pageHeight, double dismissDuration) {
8278
this.webView = webView;
8379
this.displayLocation = displayLocation;
8480
this.pageHeight = pageHeight;
8581
this.pageWidth = ViewGroup.LayoutParams.MATCH_PARENT;
86-
this.dismissDuration = dismissDuration;
82+
this.dismissDuration = Double.isNaN(dismissDuration) ? 0 : dismissDuration;
8783
this.hasBackground = !displayLocation.isBanner();
8884
}
8985

@@ -99,20 +95,6 @@ void setMessageController(InAppMessageViewListener messageController) {
9995
return displayLocation;
10096
}
10197

102-
void destroyView(WeakReference<Activity> weakReference) {
103-
// WeakReference is the Activity when onStop is called
104-
if (weakReference.get() != null) {
105-
if (draggableRelativeLayout != null) {
106-
draggableRelativeLayout.removeAllViews();
107-
}
108-
if (parentRelativeLayout != null) {
109-
removeParentLinearLayout(weakReference.get());
110-
parentRelativeLayout.removeAllViews();
111-
}
112-
}
113-
markAsDismissed();
114-
}
115-
11698
void showView(Activity activity) {
11799
delayShowUntilAvailable(activity);
118100
}
@@ -252,7 +234,7 @@ public void run() {
252234
messageController.onMessageWasShown();
253235
}
254236

255-
initDismissIfNeeded();
237+
startDismissTimerIfNeeded();
256238
}
257239
});
258240
}
@@ -298,7 +280,7 @@ private void createPopupWindow(@NonNull RelativeLayout parentRelativeLayout) {
298280
);
299281
}
300282

301-
private void setUpParentLinearLayout(Context context) {
283+
private void setUpParentLinearLayout(Context context) {
302284
parentRelativeLayout = new RelativeLayout(context);
303285
parentRelativeLayout.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
304286
parentRelativeLayout.setClipChildren(false);
@@ -321,7 +303,7 @@ void onDismiss() {
321303
});
322304

323305
if (webView.getParent() != null)
324-
((ViewGroup) webView.getParent()).removeAllViews();
306+
((ViewGroup) webView.getParent()).removeAllViews();
325307

326308
CardView cardView = createCardView(context);
327309
cardView.addView(webView);
@@ -359,24 +341,27 @@ private CardView createCardView(Context context) {
359341
}
360342

361343
/**
362-
* Schedule dismiss behavior
344+
* Schedule dismiss behavior, if IAM has a dismiss after X number of seconds timer.
363345
*/
364-
private void initDismissIfNeeded() {
365-
if (dismissDuration > 0 && dismissSchedule == null) {
366-
dismissSchedule = new Runnable() {
367-
public void run() {
368-
if (currentActivity != null) {
369-
dismissAndAwaitNextMessage(null);
370-
dismissSchedule = null;
371-
} else {
372-
//for cases when the app is on background and the dismiss is triggered
373-
shouldDismissWhenActive = true;
374-
}
375-
}
376-
};
346+
private void startDismissTimerIfNeeded() {
347+
if (dismissDuration <= 0)
348+
return;
377349

378-
handler.postDelayed(dismissSchedule, (long) dismissDuration * 1_000);
379-
}
350+
if (scheduleDismissRunnable != null)
351+
return;
352+
353+
scheduleDismissRunnable = new Runnable() {
354+
public void run() {
355+
if (currentActivity != null) {
356+
dismissAndAwaitNextMessage(null);
357+
scheduleDismissRunnable = null;
358+
} else {
359+
// For cases when the app is on background and the dismiss is triggered
360+
shouldDismissWhenActive = true;
361+
}
362+
}
363+
};
364+
handler.postDelayed(scheduleDismissRunnable, (long) dismissDuration * 1_000);
380365
}
381366

382367
// Do not add view until activity is ready
@@ -398,8 +383,8 @@ public void run() {
398383
*/
399384
void dismissAndAwaitNextMessage(@Nullable WebViewManager.OneSignalGenericCallback callback) {
400385
if (draggableRelativeLayout == null) {
401-
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "No host presenter to trigger dismiss animation, counting as dismissed already");
402-
markAsDismissed();
386+
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "No host presenter to trigger dismiss animation, counting as dismissed already", new Throwable());
387+
dereferenceViews();
403388
if (callback != null)
404389
callback.onComplete();
405390
return;
@@ -417,48 +402,47 @@ private void finishAfterDelay(final WebViewManager.OneSignalGenericCallback call
417402
OSUtils.runOnMainThreadDelayed(new Runnable() {
418403
@Override
419404
public void run() {
420-
if (hasBackground && parentRelativeLayout != null) {
405+
if (hasBackground && parentRelativeLayout != null)
421406
animateAndDismissLayout(parentRelativeLayout, callback);
422-
} else {
423-
removeViews(callback);
407+
else {
408+
cleanupViewsAfterDismiss();
409+
if (callback != null)
410+
callback.onComplete();
424411
}
425412
}
426413
}, ACTIVITY_FINISH_AFTER_DISMISS_DELAY_MS);
427414
}
428415

429416
/**
430-
* Remove references from the views
417+
* IAM has been fully dismissed, remove all views and call the onMessageWasDismissed callback
431418
*/
432-
private void removeViews(WebViewManager.OneSignalGenericCallback callback) {
433-
if (dismissSchedule != null) {
434-
//dismissed before the dismiss delay
435-
handler.removeCallbacks(dismissSchedule);
436-
dismissSchedule = null;
437-
}
438-
if (draggableRelativeLayout != null) {
439-
draggableRelativeLayout.removeAllViews();
440-
}
441-
442-
removeParentLinearLayout(currentActivity);
443-
444-
if (messageController != null) {
419+
private void cleanupViewsAfterDismiss() {
420+
removeAllViews();
421+
if (messageController != null)
445422
messageController.onMessageWasDismissed();
446-
}
447-
markAsDismissed();
448-
449-
if (callback != null)
450-
callback.onComplete();
451423
}
452424

453-
private void removeParentLinearLayout(Activity currentActivity) {
454-
if (popupWindow != null)
455-
popupWindow.dismiss();
425+
/**
426+
* Remove all views and dismiss PopupWindow
427+
*/
428+
void removeAllViews() {
429+
if (scheduleDismissRunnable != null) {
430+
// Dismissed before the dismiss delay
431+
handler.removeCallbacks(scheduleDismissRunnable);
432+
scheduleDismissRunnable = null;
433+
}
434+
if (draggableRelativeLayout != null)
435+
draggableRelativeLayout.removeAllViews();
436+
437+
if (popupWindow != null)
438+
popupWindow.dismiss();
439+
dereferenceViews();
456440
}
457441

458442
/**
459443
* Cleans all layout references so this can be cleaned up in the next GC
460444
*/
461-
private void markAsDismissed() {
445+
private void dereferenceViews() {
462446
// Dereference so this can be cleaned up in the next GC
463447
parentRelativeLayout = null;
464448
draggableRelativeLayout = null;
@@ -531,7 +515,9 @@ private void animateAndDismissLayout(View backgroundView, final WebViewManager.O
531515
Animator.AnimatorListener animCallback = new AnimatorListenerAdapter() {
532516
@Override
533517
public void onAnimationEnd(Animator animation) {
534-
removeViews(callback);
518+
cleanupViewsAfterDismiss();
519+
if (callback != null)
520+
callback.onComplete();
535521
}
536522
};
537523

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

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import android.graphics.Point;
88
import android.graphics.Rect;
99
import android.os.Build;
10-
import android.os.IBinder;
1110
import android.support.annotation.NonNull;
12-
import android.support.annotation.Nullable;
1311
import android.util.DisplayMetrics;
1412
import android.view.View;
1513
import android.view.Window;
@@ -45,9 +43,26 @@ static boolean isKeyboardUp(WeakReference<Activity> activityWeakReference) {
4543
return isOpen;
4644
}
4745

48-
49-
static void decorViewReady(@NonNull Activity activity, @NonNull Runnable runnable) {
50-
activity.getWindow().getDecorView().post(runnable);
46+
// Ensures the root decor view is ready by checking the following;
47+
// 1. Is fully attach to the root window and insets are available
48+
// 2. Ensure if any Activities are changed while waiting we use the updated one
49+
static void decorViewReady(@NonNull Activity activity, final @NonNull Runnable runnable) {
50+
final String listenerKey = "decorViewReady:" + runnable;
51+
activity.getWindow().getDecorView().post(new Runnable() {
52+
@Override
53+
public void run() {
54+
ActivityLifecycleHandler.setActivityAvailableListener(listenerKey, new ActivityLifecycleHandler.ActivityAvailableListener() {
55+
@Override
56+
void available(@NonNull Activity currentActivity) {
57+
ActivityLifecycleHandler.removeActivityAvailableListener(listenerKey);
58+
if (isActivityFullyReady(currentActivity))
59+
runnable.run();
60+
else
61+
decorViewReady(currentActivity, runnable);
62+
}
63+
});
64+
}
65+
});
5166
}
5267

5368
private static @NonNull Rect getWindowVisibleDisplayFrame(@NonNull Activity activity) {
@@ -102,8 +117,17 @@ static int dpToPx(int dp) {
102117
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
103118
}
104119

105-
// Ensures the Activity is fully attached to a top-level Window by checking if it has an IBinder
120+
// Ensures the Activity is fully ready by;
121+
// 1. Ensure it is attached to a top-level Window by checking if it has an IBinder
122+
// 2. If Android M or higher ensure WindowInsets exists on the root window also
106123
static boolean isActivityFullyReady(@NonNull Activity activity) {
107-
return activity.getWindow().getDecorView().getApplicationWindowToken() != null;
124+
boolean hasToken = activity.getWindow().getDecorView().getApplicationWindowToken() != null;
125+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
126+
return hasToken;
127+
128+
View decorView = activity.getWindow().getDecorView();
129+
boolean insetsAttached = decorView.getRootWindowInsets() != null;
130+
131+
return hasToken && insetsAttached;
108132
}
109133
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ void available(final @NonNull Activity activity) {
270270
@Override
271271
void stopped(WeakReference<Activity> reference) {
272272
if (messageView != null)
273-
messageView.destroyView(reference);
273+
messageView.removeAllViews();
274274
}
275275

276276
private void showMessageView(@Nullable Integer newHeight) {
@@ -286,7 +286,6 @@ private void showMessageView(@Nullable Integer newHeight) {
286286
messageView.checkIfShouldDismiss();
287287
}
288288

289-
// TODO: Test with chrome://crash
290289
@SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
291290
private void setupWebView(@NonNull final Activity currentActivity, final @NonNull String base64Message) {
292291
enableWebViewRemoteDebugging();
@@ -303,7 +302,7 @@ private void setupWebView(@NonNull final Activity currentActivity, final @NonNul
303302

304303
blurryRenderingWebViewForKitKatWorkAround(webView);
305304

306-
OSViewUtils.decorViewReady(activity, new Runnable() {
305+
OSViewUtils.decorViewReady(currentActivity, new Runnable() {
307306
@Override
308307
public void run() {
309308
setWebViewToMaxSize(currentActivity);
@@ -371,8 +370,11 @@ private static int getWebViewMaxSizeY(Activity activity) {
371370
* Trigger the {@link #messageView} dismiss animation flow
372371
*/
373372
protected void dismissAndAwaitNextMessage(@Nullable final OneSignalGenericCallback callback) {
374-
if (messageView == null)
373+
if (messageView == null) {
374+
if (callback != null)
375+
callback.onComplete();
375376
return;
377+
}
376378

377379
messageView.dismissAndAwaitNextMessage(new OneSignalGenericCallback() {
378380
@Override

OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import com.onesignal.ShadowGcmBroadcastReceiver;
6767
import com.onesignal.ShadowNotificationManagerCompat;
6868
import com.onesignal.ShadowOSUtils;
69+
import com.onesignal.ShadowOSViewUtils;
6970
import com.onesignal.ShadowOSWebView;
7071
import com.onesignal.ShadowOneSignal;
7172
import com.onesignal.ShadowOneSignalRestClient;
@@ -133,6 +134,7 @@
133134
ShadowBadgeCountUpdater.class,
134135
ShadowNotificationManagerCompat.class,
135136
ShadowOSUtils.class,
137+
ShadowOSViewUtils.class
136138
},
137139
sdk = 21)
138140
@RunWith(RobolectricTestRunner.class)

0 commit comments

Comments
 (0)