77import android .app .Activity ;
88import android .content .Context ;
99import android .content .Intent ;
10+ import android .graphics .Bitmap ;
1011import android .graphics .drawable .ColorDrawable ;
1112import android .graphics .drawable .Drawable ;
1213import android .net .Uri ;
1314import android .os .Build ;
1415import android .os .Bundle ;
1516import android .os .Message ;
17+ import android .util .DisplayMetrics ;
18+ import android .util .TypedValue ;
1619import android .view .Menu ;
1720import android .view .MenuItem ;
21+ import android .view .View ;
22+ import android .view .ViewGroup ;
1823import android .webkit .ValueCallback ;
1924import android .webkit .WebChromeClient ;
2025import android .webkit .WebResourceRequest ;
2126import android .webkit .WebSettings ;
2227import android .webkit .WebView ;
2328import android .webkit .WebViewClient ;
29+ import android .widget .FrameLayout ;
2430
2531import androidx .annotation .ColorInt ;
2632import androidx .annotation .DrawableRes ;
2733import androidx .annotation .NonNull ;
2834import androidx .annotation .Nullable ;
2935import androidx .appcompat .app .ActionBar ;
3036import androidx .appcompat .app .AppCompatActivity ;
37+ import androidx .appcompat .widget .Toolbar ;
3138import androidx .core .content .res .ResourcesCompat ;
39+ import androidx .core .graphics .Insets ;
3240import androidx .core .graphics .drawable .DrawableCompat ;
41+ import androidx .core .util .TypedValueCompat ;
42+ import androidx .core .view .OnApplyWindowInsetsListener ;
43+ import androidx .core .view .ViewCompat ;
44+ import androidx .core .view .WindowInsetsCompat ;
3345
3446public class WebKitWebViewActivity extends AppCompatActivity {
3547
@@ -38,7 +50,11 @@ public class WebKitWebViewActivity extends AppCompatActivity {
3850 private static final int MENU_ID_CANCEL = 1 ;
3951 private static final int TAG_FILE_CHOOSER = 1 ;
4052
53+ private FrameLayout mRootFrameLayout ;
54+ private Toolbar mToolbar ;
55+ private FrameLayout mToolbarFrameLayout ;
4156 private WebView mWebView ;
57+ private Insets mLastSeenInsets ;
4258 private Uri result ;
4359 private StartActivityHandles <ValueCallback <Uri []>> handles = new StartActivityHandles <>();
4460
@@ -85,6 +101,19 @@ private MyWebViewClient(WebKitWebViewActivity activity) {
85101 this .activity = activity ;
86102 }
87103
104+ @ Override
105+ public void onPageStarted (WebView view , String url , Bitmap favicon ) {
106+ super .onPageStarted (view , url , favicon );
107+ // onPageStarted is not always called, but when it is called, it is called before
108+ // onPageFinished.
109+ // Therefore, we put the edge-to-edge handling here hoping that
110+ // the safe area insets can be set as soon as possible.
111+
112+ view .evaluateJavascript (USERSCRIPT_USER_SELECT_NONE , null );
113+ this .activity .handleNonEdgeToEdge ();
114+ this .activity .handleEdgeToEdge ();
115+ }
116+
88117 @ Override
89118 public void onPageFinished (WebView view , String url ) {
90119 super .onPageFinished (view , url );
@@ -96,6 +125,8 @@ public void onPageFinished(WebView view, String url) {
96125 // The caveat is that the script is run in the main frame only.
97126 // But we do not actually use iframes so it does not matter.
98127 view .evaluateJavascript (USERSCRIPT_USER_SELECT_NONE , null );
128+ this .activity .handleNonEdgeToEdge ();
129+ this .activity .handleEdgeToEdge ();
99130 }
100131
101132 @ TargetApi (Build .VERSION_CODES .N )
@@ -199,18 +230,85 @@ public static Intent createIntent(Context ctx, Options options) {
199230 return intent ;
200231 }
201232
233+ private float getActionBarSizeInDp () {
234+ float actionBarSizeInDp = 44f ;
235+ TypedValue tv = new TypedValue ();
236+ if (this .getTheme ().resolveAttribute (android .R .attr .actionBarSize , tv , true )) {
237+ int actionBarSizeInPx = TypedValue .complexToDimensionPixelSize (tv .data , this .getResources ().getDisplayMetrics ());
238+ actionBarSizeInDp = TypedValueCompat .pxToDp ((float ) actionBarSizeInPx , this .getResources ().getDisplayMetrics ());
239+ }
240+ return actionBarSizeInDp ;
241+ }
242+
243+ private void applyInsetsToWebView (Insets safeAreaInsets ) {
244+ float actionBarSizeInDp = this .getActionBarSizeInDp ();
245+ DisplayMetrics displayMetrics = this .getResources ().getDisplayMetrics ();
246+ float actionBarSizeInPx = TypedValueCompat .dpToPx (actionBarSizeInDp , displayMetrics );
247+ float top = TypedValueCompat .pxToDp ((float ) safeAreaInsets .top + actionBarSizeInPx , displayMetrics );
248+ float right = TypedValueCompat .pxToDp ((float ) safeAreaInsets .right , displayMetrics );
249+ float bottom = TypedValueCompat .pxToDp ((float ) safeAreaInsets .bottom , displayMetrics );
250+ float left = TypedValueCompat .pxToDp ((float ) safeAreaInsets .left , displayMetrics );
251+
252+ String safeAreaJs =
253+ "document.documentElement.style.setProperty('--safe-area-inset-top', '" + top + "px');\n " +
254+ "document.documentElement.style.setProperty('--safe-area-inset-right', '" + right + "px');\n " +
255+ "document.documentElement.style.setProperty('--safe-area-inset-bottom', '" + bottom + "px');\n " +
256+ "document.documentElement.style.setProperty('--safe-area-inset-left', '" + left + "px');" ;
257+
258+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .KITKAT ) {
259+ this .mWebView .evaluateJavascript (safeAreaJs , null );
260+ }
261+ }
262+
263+ private void handleNonEdgeToEdge () {
264+ // In non edge-to-edge, the insets listener is not called.
265+ // So we have to apply the insets here.
266+ Insets insets = this .mLastSeenInsets == null ? Insets .NONE : this .mLastSeenInsets ;
267+ this .applyInsetsToWebView (insets );
268+ }
269+
270+ private void handleEdgeToEdge () {
271+ // In edge-to-edge, we ask the system to invoke the insets listener.
272+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .KITKAT_WATCH ) {
273+ this .mRootFrameLayout .requestApplyInsets ();
274+ }
275+ }
276+
202277 @ Override
203278 protected void onCreate (@ Nullable Bundle savedInstanceState ) {
204279 super .onCreate (savedInstanceState );
205280
281+ this .mRootFrameLayout = new FrameLayout (this );
282+ this .mRootFrameLayout .setLayoutParams (new ViewGroup .LayoutParams (
283+ ViewGroup .LayoutParams .MATCH_PARENT ,
284+ ViewGroup .LayoutParams .MATCH_PARENT
285+ ));
286+
287+ this .mToolbarFrameLayout = new FrameLayout (this );
288+ this .mToolbarFrameLayout .setLayoutParams (new ViewGroup .LayoutParams (
289+ ViewGroup .LayoutParams .MATCH_PARENT ,
290+ ViewGroup .LayoutParams .WRAP_CONTENT
291+ ));
292+
293+ float actionBarSizeInDp = this .getActionBarSizeInDp ();
294+
295+ this .mToolbar = new Toolbar (this );
296+ this .mToolbar .setLayoutParams (new FrameLayout .LayoutParams (
297+ ViewGroup .LayoutParams .MATCH_PARENT ,
298+ (int ) TypedValueCompat .dpToPx (actionBarSizeInDp , this .getResources ().getDisplayMetrics ())
299+ ));
300+ this .setSupportActionBar (this .mToolbar );
301+
206302 Options options = this .getOptions ();
207303
208304 // Do not show title.
209305 getSupportActionBar ().setDisplayShowTitleEnabled (false );
210306
211307 // Configure navigation bar background color.
212308 if (options .actionBarBackgroundColor != null ) {
213- getSupportActionBar ().setBackgroundDrawable (new ColorDrawable (options .actionBarBackgroundColor ));
309+ ColorDrawable colorDrawable = new ColorDrawable (options .actionBarBackgroundColor );
310+ getSupportActionBar ().setBackgroundDrawable (colorDrawable );
311+ this .mToolbarFrameLayout .setBackgroundDrawable (colorDrawable );
214312 }
215313
216314 // Show back button.
@@ -225,17 +323,62 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
225323
226324 // Configure web view.
227325 this .mWebView = new WebView (this );
326+ this .mWebView .setLayoutParams (new FrameLayout .LayoutParams (
327+ ViewGroup .LayoutParams .MATCH_PARENT ,
328+ ViewGroup .LayoutParams .MATCH_PARENT
329+ ));
228330 this .mWebView .getSettings ().setSupportMultipleWindows (true );
229331 this .mWebView .getSettings ().setDomStorageEnabled (true );
230- this .setContentView (this .mWebView );
231332 this .mWebView .setWebViewClient (new MyWebViewClient (this ));
232333 this .mWebView .setWebChromeClient (new MyWebChromeClient (this ));
233334 WebSettings webSettings = this .mWebView .getSettings ();
234335 webSettings .setJavaScriptEnabled (true );
235336
236- if (savedInstanceState == null ) {
237- this .mWebView .loadUrl (options .url .toString ());
238- }
337+ this .mRootFrameLayout .addView (this .mWebView );
338+ this .mRootFrameLayout .addView (this .mToolbarFrameLayout );
339+ this .mToolbarFrameLayout .addView (this .mToolbar );
340+ this .setContentView (this .mRootFrameLayout );
341+
342+ ViewCompat .setOnApplyWindowInsetsListener (this .mRootFrameLayout , new OnApplyWindowInsetsListener () {
343+
344+ @ Override
345+ public WindowInsetsCompat onApplyWindowInsets (View v , WindowInsetsCompat insets ) {
346+ Insets safeAreaInsets = insets .getInsets (
347+ WindowInsetsCompat .Type .systemBars () |
348+ WindowInsetsCompat .Type .displayCutout () |
349+ WindowInsetsCompat .Type .ime ()
350+ );
351+
352+ WebKitWebViewActivity .this .mLastSeenInsets = safeAreaInsets ;
353+
354+ ViewGroup .MarginLayoutParams toolbarParams = (ViewGroup .MarginLayoutParams ) mToolbar .getLayoutParams ();
355+ toolbarParams .setMargins (
356+ safeAreaInsets .left ,
357+ safeAreaInsets .top ,
358+ safeAreaInsets .right ,
359+ 0
360+ );
361+
362+ WebKitWebViewActivity .this .applyInsetsToWebView (safeAreaInsets );
363+
364+ return WindowInsetsCompat .CONSUMED ;
365+ }
366+ });
367+
368+ this .mRootFrameLayout .post (new Runnable () {
369+ @ Override
370+ public void run () {
371+ // We want the content view to draw at least once before loading the URL.
372+ //
373+ // In non edge-to-edge, the insets listener is never called so mLastSeenInsets is null.
374+ //
375+ // In edge-to-edge, the insets listener will be called at least once in the first draw,
376+ // so by the time onPageStart / onPageFinished is called, mLastSeenInsets is not null.
377+ if (savedInstanceState == null ) {
378+ WebKitWebViewActivity .this .mWebView .loadUrl (options .url .toString ());
379+ }
380+ }
381+ });
239382 }
240383
241384 @ Override
0 commit comments