Repository: flutter
Issue: Race condition between FlutterEngineGroup and FlutterActivity results in locale name of "und"
Labels: platform-android,engine,has reproducible steps,P2,found in release: 2.10,found in release: 2.11,team-android,triaged-android

## Steps to reproduce

1. Execute `flutter run --release` on the code sample with an Android phone.

**Expected results:** The app displays a centered Text widget with "Locale: en_US"

**Actual results:** The app displays a centered Text widget with "Locale: und"

The issue is a race condition that occurs when using `FlutterEngineGroup` to provide a `FlutterEngine` to a `FlutterActivity`. 

<details>
<summary>RootCause</summary>

The root cause of issue seems to be that `FlutterEngineGroup` requires an entrypoint to be specified when the engine is created and it executes the entrypoint immediately. As a result, the engine returned by `FlutterActivity.provideFlutterEngine()` is already running. However, the `FlutterActivityAndFragmentDelegate.onAttach()` method, which indirectly called `provideFutterEngine()`, then sets up the `PlatformPlugin` using `host.providePlatformPlugin()` and calls `host.configureFlutterEngine()`.

Since the engine is already running when it's returned from `FlutterActivity.provideFlutterEngine()`, there is a race between when the flutter code tries to access `Platform.localeName` and when the native code calls `host.providePlatformPlugin()`
</details>

<details>
<summary>Code Sample</summary>

A flutter project containing the following code that illustrates the issue can be found at: https://github.com/champeauxr/engine_group_race_condition

The Flutter UI just centers a `Text` widget that displays the current locale name:
```dart
import 'dart:io';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Locale: ${Platform.localeName}'),
        ),
      ),
    );
  }
}
```

The native Android code simply overrides the `provideFlutterEngine()` and `shouldDestroyEngineWithHost()` methods of `FlutterActivity` to provide an engine created using the `FlutterEngineGroup`.
```kotlin
package com.example.engine_group_race_condition

import android.content.Context
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineGroup

var engineGroup: FlutterEngineGroup? = null

class MainActivity: FlutterActivity() {
    override fun shouldDestroyEngineWithHost(): Boolean {
        return true;
    }

    override fun provideFlutterEngine(context: Context): FlutterEngine {
        if (engineGroup == null) {
            engineGroup = FlutterEngineGroup(this)
        }

        val engine = engineGroup!!.createAndRunDefaultEngine(this)

        // This sleep() ensures that the flutter code started by createAndRunDefaultEngine() wins
        // the race with FlutterActivity's call to either host.providePlatformPlugin() or
        // perhaps host.configureFlutterEngine()
        Thread.sleep(500);

        return engine;
    }
}
```

A sleep() was added after creating the engine to ensure that the Flutter code wins the race to consistently demonstrate the issue. Without this sleep(), the problem is intermittent and happens more often than not on a release build, and with much less frequency on a debug build. Also, when running a debug build, the `Text` widget displays "Locale: und" for a fraction of a second before refreshing and displaying "Locale: en_US". However with a release build, the "Locale: und" remains in the `Text` widget.
</details>

<details>
<summary>Additional Investigation Notes</summary>

`FlutterActivity.onCreate()` calls `FlutterActivityAndFragmentDelegate.onAttach()`
```java
  void onAttach(@NonNull Context context) {
    ensureAlive();

    // When "retain instance" is true, the FlutterEngine will survive configuration
    // changes. Therefore, we create a new one only if one does not already exist.
    if (flutterEngine == null) {
      setupFlutterEngine();
    }

    if (host.shouldAttachEngineToActivity()) {
      // Notify any plugins that are currently attached to our FlutterEngine that they
      // are now attached to an Activity.
      //
      // Passing this Fragment's Lifecycle should be sufficient because as long as this Fragment
      // is attached to its Activity, the lifecycles should be in sync. Once this Fragment is
      // detached from its Activity, that Activity will be detached from the FlutterEngine, too,
      // which means there shouldn't be any possibility for the Fragment Lifecycle to get out of
      // sync with the Activity. We use the Fragment's Lifecycle because it is possible that the
      // attached Activity is not a LifecycleOwner.
      Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this delegate.");
      flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
    }

    // Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
    // is bound to a specific Activity. Therefore, it needs to be created and configured
    // every time this Fragment attaches to a new Activity.
    // TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
    //                    control of the entire window. This is unacceptable for non-fullscreen
    //                    use-cases.
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

    host.configureFlutterEngine(flutterEngine);
    isAttached = true;
  }
```
The call to `setupFlutterEngine()` is what calls `FlutterActivity.provideFlutterEngine()` and when `setupFlutterEngine()` returns, the engine is already running the Flutter code.

At the end of `onAttach()`, it runs the following two lines:
```java
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

    host.configureFlutterEngine(flutterEngine);
```

Presumably, one of these two lines connects up the `PlatformPlugin` to the Flutter engine and prior to these being called, the Flutter code would not be able to retrieve the locale name from `Platform.localeName`. However the FlutterEngine produced by the `FlutterEngineGroup` is already running Flutter code before we get here and may have already attempted to retrieve the locale name.

In normal scenarios, where the `FlutterEngineGroup` is not used, the entrypoint on the Flutter engine isn't executed until `FlutterActivityAndFragmentDelegate.onStart()` is called by `FlutterActivity.onStart()`
```java
  void onStart() {
    Log.v(TAG, "onStart()");
    ensureAlive();
    doInitialFlutterViewRun();
  }
```

It would seem that `FlutterEngineGroup` should provide a method that creates an engine without executing an entrypoint. This would allow other initialization, such as that performed by `FlutterActivityAndFragmentDelegate` to be performed before the entrypoint is executed.
</details>

<details>
<summary>FlutterDoctor output</summary>

```
[√] Flutter (Channel stable, 2.10.0, on Microsoft Windows [Version 10.0.19043.1526], locale en-US)
    • Flutter version 2.10.0 at E:\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 5f105a6ca7 (4 weeks ago), 2022-02-01 14:15:42 -0800
    • Engine revision 776efd2034
    • Dart version 2.16.0
    • DevTools version 2.9.2

[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    • Android SDK at E:\Android\Sdk
    • Platform android-31, build-tools 31.0.0
    • ANDROID_SDK_ROOT = E:\Android\Sdk
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop for Windows (Visual Studio Professional 2022 17.0.6)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Professional
    • Visual Studio Professional 2022 version 17.0.32126.317
    • Windows 10 SDK version 10.0.19041.0

[√] Android Studio (version 2021.1)
    • Android Studio at C:\Program Files\Android\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)

[√] VS Code, 64-bit edition (version 1.61.0)
    • VS Code at C:\Program Files\Microsoft VS Code
    • Flutter extension version 3.29.0

[√] Connected device (4 available)
    • SM G781U1 (mobile) • RFCR11D2E3V • android-arm64  • Android 11 (API 30)
    • Windows (desktop)  • windows     • windows-x64    • Microsoft Windows [Version 10.0.19043.1526]
    • Chrome (web)       • chrome      • web-javascript • Google Chrome 99.0.4844.51
    • Edge (web)         • edge        • web-javascript • Microsoft Edge 96.0.1054.62

[√] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!
```
</details>