1 package devmenu.com.th3rdwave.safeareacontext;
2 
3 import android.graphics.Rect;
4 import android.os.Build;
5 import android.view.View;
6 import android.view.ViewGroup;
7 import android.view.WindowInsets;
8 
9 import androidx.annotation.Nullable;
10 
11 /* package */ class SafeAreaUtils {
12 
getRootWindowInsetsCompat(View rootView)13   private static @Nullable EdgeInsets getRootWindowInsetsCompat(View rootView) {
14     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
15       WindowInsets insets = rootView.getRootWindowInsets();
16       if (insets == null) {
17         return null;
18       }
19       return new EdgeInsets(
20         insets.getSystemWindowInsetTop(),
21         insets.getSystemWindowInsetRight(),
22         // System insets are more reliable to account for notches but the
23         // system inset for bottom includes the soft keyboard which we don't
24         // want to be consistent with iOS. Using the min value makes sure we
25         // never get the keyboard offset while still working with devices that
26         // hide the navigation bar.
27         Math.min(insets.getSystemWindowInsetBottom(), insets.getStableInsetBottom()),
28         insets.getSystemWindowInsetLeft());
29     } else {
30       Rect visibleRect = new Rect();
31       rootView.getWindowVisibleDisplayFrame(visibleRect);
32       return new EdgeInsets(
33         visibleRect.top,
34         rootView.getWidth() - visibleRect.right,
35         rootView.getHeight() - visibleRect.bottom,
36         visibleRect.left);
37     }
38   }
39 
getSafeAreaInsets(View view)40   static @Nullable EdgeInsets getSafeAreaInsets(View view) {
41     // The view has not been layout yet.
42     if (view.getHeight() == 0) {
43       return null;
44     }
45     View rootView = view.getRootView();
46     EdgeInsets windowInsets = getRootWindowInsetsCompat(rootView);
47     if (windowInsets == null) {
48       return null;
49     }
50 
51     // Calculate the part of the view that overlaps with window insets.
52     float windowWidth = rootView.getWidth();
53     float windowHeight = rootView.getHeight();
54     Rect visibleRect = new Rect();
55     view.getGlobalVisibleRect(visibleRect);
56 
57     windowInsets.setTop(Math.max(windowInsets.getTop() - visibleRect.top, 0));
58     windowInsets.setLeft(Math.max(windowInsets.getLeft() - visibleRect.left, 0));
59     windowInsets.setBottom(Math.max(Math.min(visibleRect.top + view.getHeight() - windowHeight, 0) + windowInsets.getBottom(), 0));
60     windowInsets.setRight(Math.max(Math.min(visibleRect.left + view.getWidth() - windowWidth, 0) + windowInsets.getRight(), 0));
61     return windowInsets;
62   }
63 
getFrame(ViewGroup rootView, View view)64   static @Nullable devmenu.com.th3rdwave.safeareacontext.Rect getFrame(ViewGroup rootView, View view) {
65     // This can happen while the view gets unmounted.
66     if (view.getParent() == null) {
67       return null;
68     }
69     Rect offset = new Rect();
70     view.getDrawingRect(offset);
71     try {
72       rootView.offsetDescendantRectToMyCoords(view, offset);
73     } catch (IllegalArgumentException ex) {
74       // This can throw if the view is not a descendant of rootView. This should not
75       // happen but avoid potential crashes.
76       ex.printStackTrace();
77       return null;
78     }
79 
80     return new devmenu.com.th3rdwave.safeareacontext.Rect(offset.left, offset.top, view.getWidth(), view.getHeight());
81   }
82 }
83