drop/restore focus when app becomes invisible/visible (#24744)

diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 90735ca..ec824a4 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -12,6 +12,7 @@
 import 'banner.dart';
 import 'basic.dart';
 import 'binding.dart';
+import 'focus_manager.dart';
 import 'framework.dart';
 import 'localizations.dart';
 import 'media_query.dart';
@@ -730,7 +731,33 @@
   }
 
   @override
-  void didChangeAppLifecycleState(AppLifecycleState state) { }
+  void didChangeAppLifecycleState(AppLifecycleState state) {
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+        return;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        // When the application moves to the background, any focus nodes
+        // need to lose focus. When the application is restored to the
+        // foreground state, any focused node should regain focus (including)
+        // restoring the keyboard. This is only important in cases where
+        // applications in the background are partially visible.
+        switch (state) {
+          case AppLifecycleState.paused:
+          case AppLifecycleState.suspending:
+          case AppLifecycleState.inactive: {
+            final FocusManager focusManager = WidgetsBinding.instance.focusManager;
+            focusManager.windowDidLoseFocus();
+            break;
+          }
+          case AppLifecycleState.resumed: {
+            final FocusManager focusManager = WidgetsBinding.instance.focusManager;
+            focusManager.windowDidGainFocus();
+            break;
+          }
+        }
+    }
+  }
 
   @override
   void didHaveMemoryPressure() { }
diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart
index 3453c26..fb32f30 100644
--- a/packages/flutter/lib/src/widgets/focus_manager.dart
+++ b/packages/flutter/lib/src/widgets/focus_manager.dart
@@ -417,6 +417,45 @@
   final FocusScopeNode rootScope = FocusScopeNode();
 
   FocusNode _currentFocus;
+  bool _windowHasFocus = true;
+  FocusNode _cachedFocus;
+  FocusScopeNode _cachedScope;
+
+  /// Call when the window loses focus.
+  ///
+  /// The currently focused node and containg scope is cached, then the current
+  /// focus is removed. For example, this ensures that cursors do not blink
+  /// while in the background.
+  ///
+  /// This method is safe to call multiple times.
+  void windowDidLoseFocus() {
+    if (!_windowHasFocus) {
+      return;
+    }
+    _windowHasFocus = false;
+    _cachedFocus = _currentFocus;
+    _cachedScope = _cachedFocus._parent;
+    _currentFocus?.unfocus();
+  }
+
+  /// Call when the window regains focus.
+  ///
+  /// If there is a cached focus node from when the window lost focus, this
+  /// method will restore focus to that node. For example, this ensures that
+  /// a focused text node will re-request the keyboard.
+  ///
+  /// This method is safe to call multiple times.
+  void windowDidGainFocus() {
+    if (_windowHasFocus) {
+      return;
+    }
+    _windowHasFocus = true;
+    if (_cachedFocus != null) {
+      _cachedScope._setFocus(_cachedFocus);
+      _cachedFocus = null;
+      _cachedScope = null;
+    }
+  }
 
   void _willDisposeFocusNode(FocusNode node) {
     assert(node != null);
@@ -434,16 +473,18 @@
 
   FocusNode _findNextFocus() {
     FocusScopeNode scope = rootScope;
-    while (scope._firstChild != null)
+    while (scope._firstChild != null) {
       scope = scope._firstChild;
+    }
     return scope._focus;
   }
 
   void _update() {
     _haveScheduledUpdate = false;
     final FocusNode nextFocus = _findNextFocus();
-    if (_currentFocus == nextFocus)
+    if (_currentFocus == nextFocus) {
       return;
+    }
     final FocusNode previousFocus = _currentFocus;
     _currentFocus = nextFocus;
     previousFocus?._notify();
diff --git a/packages/flutter/test/widgets/focus_test.dart b/packages/flutter/test/widgets/focus_test.dart
index c02633b..3fd0981 100644
--- a/packages/flutter/test/widgets/focus_test.dart
+++ b/packages/flutter/test/widgets/focus_test.dart
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/widgets.dart';
 
@@ -243,4 +246,29 @@
 
     parentFocusScope.detach();
   });
+
+  testWidgets('Focus is lost/restored when window focus is lost/restored on Fuchsia', (WidgetTester tester) async {
+    debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
+    final FocusNode node = FocusNode();
+    await tester.pumpWidget(MaterialApp(
+      home: Scaffold(
+        body: TextField(
+          focusNode: node,
+          autofocus: true,
+        )
+      ),
+    ));
+    expect(node.hasFocus, true);
+    ByteData message = const StringCodec().encodeMessage('AppLifecycleState.inactive');
+    await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
+    await tester.pump();
+    // Focus is lost when app goes to background.
+    expect(node.hasFocus, false);
+    message = const StringCodec().encodeMessage('AppLifecycleState.resumed');
+    await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
+    await tester.pump();
+    // Focus is restored.
+    expect(node.hasFocus, true);
+    debugDefaultTargetPlatformOverride = null;
+  });
 }