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