Flutter Engine
The Flutter Engine
FlutterFragmentActivity.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.embedding.android;
6
7import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY;
8import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_URI_META_DATA_KEY;
9import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE;
10import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
11import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
12import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
13import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID;
14import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
15import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT;
16import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS;
17import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
18import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
19import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
20import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
21import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;
22
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ActivityInfo;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager;
28import android.graphics.Color;
29import android.graphics.drawable.ColorDrawable;
30import android.os.Bundle;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.Window;
34import android.view.WindowManager;
35import android.widget.FrameLayout;
36import androidx.annotation.NonNull;
37import androidx.annotation.Nullable;
38import androidx.annotation.VisibleForTesting;
39import androidx.fragment.app.FragmentActivity;
40import androidx.fragment.app.FragmentManager;
41import io.flutter.Log;
42import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
43import io.flutter.embedding.engine.FlutterEngine;
44import io.flutter.embedding.engine.FlutterShellArgs;
45import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
46import io.flutter.plugin.platform.PlatformPlugin;
47import java.util.ArrayList;
48import java.util.List;
49
50/**
51 * A Flutter {@code Activity} that is based upon {@link FragmentActivity}.
52 *
53 * <p>{@code FlutterFragmentActivity} exists because there are some Android APIs in the ecosystem
54 * that only accept a {@link FragmentActivity}. If a {@link FragmentActivity} is not required, you
55 * should consider using a regular {@link FlutterActivity} instead, because {@link FlutterActivity}
56 * is considered to be the standard, canonical implementation of a Flutter {@code Activity}.
57 */
58// A number of methods in this class have the same implementation as FlutterActivity. These methods
59// are duplicated for readability purposes. Be sure to replicate any change in this class in
60// FlutterActivity, too.
61public class FlutterFragmentActivity extends FragmentActivity
63 private static final String TAG = "FlutterFragmentActivity";
64
65 // FlutterFragment management.
66 private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
67 // TODO(mattcarroll): replace ID with R.id when build system supports R.java
68 public static final int FRAGMENT_CONTAINER_ID = View.generateViewId();
69
70 /**
71 * Creates an {@link Intent} that launches a {@code FlutterFragmentActivity}, which executes a
72 * {@code main()} Dart entrypoint, and displays the "/" route as Flutter's initial route.
73 */
74 @NonNull
75 public static Intent createDefaultIntent(@NonNull Context launchContext) {
76 return withNewEngine().build(launchContext);
77 }
78
79 /**
80 * Creates an {@link FlutterFragmentActivity.NewEngineIntentBuilder}, which can be used to
81 * configure an {@link Intent} to launch a {@code FlutterFragmentActivity} that internally creates
82 * a new {@link io.flutter.embedding.engine.FlutterEngine} using the desired Dart entrypoint,
83 * initial route, etc.
84 */
85 @NonNull
88 }
89
90 /**
91 * Builder to create an {@code Intent} that launches a {@code FlutterFragmentActivity} with a new
92 * {@link io.flutter.embedding.engine.FlutterEngine} and the desired configuration.
93 */
94 public static class NewEngineIntentBuilder {
95 private final Class<? extends FlutterFragmentActivity> activityClass;
96 private String initialRoute = DEFAULT_INITIAL_ROUTE;
97 private String backgroundMode = DEFAULT_BACKGROUND_MODE;
98 @Nullable private List<String> dartEntrypointArgs;
99
100 /**
101 * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
102 * {@code FlutterFragmentActivity}.
103 *
104 * <p>Subclasses of {@code FlutterFragmentActivity} should provide their own static version of
105 * {@link #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder}
106 * constructed with a {@code Class} reference to the {@code FlutterFragmentActivity} subclass,
107 * e.g.:
108 *
109 * <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }
110 */
111 public NewEngineIntentBuilder(@NonNull Class<? extends FlutterFragmentActivity> activityClass) {
112 this.activityClass = activityClass;
113 }
114
115 /**
116 * The initial route that a Flutter app will render in this {@code FlutterFragmentActivity},
117 * defaults to "/".
118 */
119 @NonNull
120 public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
121 this.initialRoute = initialRoute;
122 return this;
123 }
124
125 /**
126 * The mode of {@code FlutterFragmentActivity}'s background, either {@link
127 * BackgroundMode#opaque} or {@link BackgroundMode#transparent}.
128 *
129 * <p>The default background mode is {@link BackgroundMode#opaque}.
130 *
131 * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
132 * {@link FlutterView} of this {@code FlutterFragmentActivity} to be configured with a {@link
133 * FlutterTextureView} to support transparency. This choice has a non-trivial performance
134 * impact. A transparent background should only be used if it is necessary for the app design
135 * being implemented.
136 *
137 * <p>A {@code FlutterFragmentActivity} that is configured with a background mode of {@link
138 * BackgroundMode#transparent} must have a theme applied to it that includes the following
139 * property: {@code <item name="android:windowIsTranslucent">true</item>}.
140 */
141 @NonNull
142 public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
143 this.backgroundMode = backgroundMode.name();
144 return this;
145 }
146
147 /**
148 * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint
149 * function.
150 *
151 * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
152 *
153 * @param dartEntrypointArgs The Dart entrypoint arguments.
154 * @return The engine intent builder.
155 */
156 @NonNull
157 public NewEngineIntentBuilder dartEntrypointArgs(@Nullable List<String> dartEntrypointArgs) {
158 this.dartEntrypointArgs = dartEntrypointArgs;
159 return this;
160 }
161
162 /**
163 * Creates and returns an {@link Intent} that will launch a {@code FlutterFragmentActivity} with
164 * the desired configuration.
165 */
166 @NonNull
167 public Intent build(@NonNull Context context) {
168 Intent intent =
169 new Intent(context, activityClass)
170 .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
171 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
172 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
173 if (dartEntrypointArgs != null) {
174 intent.putExtra(EXTRA_DART_ENTRYPOINT_ARGS, new ArrayList(dartEntrypointArgs));
175 }
176 return intent;
177 }
178 }
179
180 /**
181 * Creates a {@link CachedEngineIntentBuilder}, which can be used to configure an {@link Intent}
182 * to launch a {@code FlutterFragmentActivity} that internally uses an existing {@link
183 * FlutterEngine} that is cached in {@link io.flutter.embedding.engine.FlutterEngineCache}.
184 */
185 @NonNull
186 public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
187 return new CachedEngineIntentBuilder(FlutterFragmentActivity.class, cachedEngineId);
188 }
189
190 /**
191 * Builder to create an {@code Intent} that launches a {@code FlutterFragmentActivity} with an
192 * existing {@link io.flutter.embedding.engine.FlutterEngine} that is cached in {@link
193 * io.flutter.embedding.engine.FlutterEngineCache}.
194 */
195 public static class CachedEngineIntentBuilder {
196 private final Class<? extends FlutterFragmentActivity> activityClass;
197 private final String cachedEngineId;
198 private boolean destroyEngineWithActivity = false;
199 private String backgroundMode = DEFAULT_BACKGROUND_MODE;
200
201 /**
202 * Constructor that allows this {@code CachedEngineIntentBuilder} to be used by subclasses of
203 * {@code FlutterFragmentActivity}.
204 *
205 * <p>Subclasses of {@code FlutterFragmentActivity} should provide their own static version of
206 * {@link #withCachedEngine(String)}, which returns an instance of {@code
207 * CachedEngineIntentBuilder} constructed with a {@code Class} reference to the {@code
208 * FlutterFragmentActivity} subclass, e.g.:
209 *
210 * <p>{@code return new CachedEngineIntentBuilder(MyFlutterActivity.class, engineId); }
211 */
213 @NonNull Class<? extends FlutterFragmentActivity> activityClass, @NonNull String engineId) {
214 this.activityClass = activityClass;
215 this.cachedEngineId = engineId;
216 }
217
218 /**
219 * Returns true if the cached {@link io.flutter.embedding.engine.FlutterEngine} should be
220 * destroyed and removed from the cache when this {@code FlutterFragmentActivity} is destroyed.
221 *
222 * <p>The default value is {@code false}.
223 */
224 public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
225 this.destroyEngineWithActivity = destroyEngineWithActivity;
226 return this;
227 }
228
229 /**
230 * The mode of {@code FlutterFragmentActivity}'s background, either {@link
231 * BackgroundMode#opaque} or {@link BackgroundMode#transparent}.
232 *
233 * <p>The default background mode is {@link BackgroundMode#opaque}.
234 *
235 * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
236 * {@link FlutterView} of this {@code FlutterFragmentActivity} to be configured with a {@link
237 * FlutterTextureView} to support transparency. This choice has a non-trivial performance
238 * impact. A transparent background should only be used if it is necessary for the app design
239 * being implemented.
240 *
241 * <p>A {@code FlutterFragmentActivity} that is configured with a background mode of {@link
242 * BackgroundMode#transparent} must have a theme applied to it that includes the following
243 * property: {@code <item name="android:windowIsTranslucent">true</item>}.
244 */
245 @NonNull
247 this.backgroundMode = backgroundMode.name();
248 return this;
249 }
250
251 /**
252 * Creates and returns an {@link Intent} that will launch a {@code FlutterFragmentActivity} with
253 * the desired configuration.
254 */
255 @NonNull
256 public Intent build(@NonNull Context context) {
257 return new Intent(context, activityClass)
258 .putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
259 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
260 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
261 }
262 }
263
264 /**
265 * Creates a {@link NewEngineInGroupIntentBuilder}, which can be used to configure an {@link
266 * Intent} to launch a {@code FlutterFragmentActivity} that internally uses an existing {@link
267 * io.flutter.embedding.engine.FlutterEngineGroup} that is cached in {@link
268 * io.flutter.embedding.engine.FlutterEngineGroupCache}.
269 *
270 * @param engineGroupId A cached engine group ID.
271 * @return The builder.
272 */
273 public static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId) {
274 return new NewEngineInGroupIntentBuilder(FlutterFragmentActivity.class, engineGroupId);
275 }
276
277 /**
278 * Builder to create an {@code Intent} that launches a {@code FlutterFragmentActivity} with a new
279 * {@link FlutterEngine} by FlutterEngineGroup#createAndRunEngine.
280 */
281 public static class NewEngineInGroupIntentBuilder {
282 private final Class<? extends FlutterFragmentActivity> activityClass;
283 private final String cachedEngineGroupId;
284 private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
285 private String initialRoute = DEFAULT_INITIAL_ROUTE;
286 private String backgroundMode = DEFAULT_BACKGROUND_MODE;
287
288 /**
289 * Constructor that allows this {@code NewEngineInGroupIntentBuilder} to be used by subclasses
290 * of {@code FlutterActivity}.
291 *
292 * <p>Subclasses of {@code FlutterFragmentActivity} should provide their own static version of
293 * {@link #withNewEngineInGroup}, which returns an instance of {@code
294 * NewEngineInGroupIntentBuilder} constructed with a {@code Class} reference to the {@code
295 * FlutterFragmentActivity} subclass, e.g.:
296 *
297 * <p>{@code return new NewEngineInGroupIntentBuilder(FlutterFragmentActivity.class,
298 * cacheedEngineGroupId); }
299 *
300 * @param activityClass A subclass of {@code FlutterFragmentActivity}.
301 * @param engineGroupId The engine group id.
302 */
304 @NonNull Class<? extends FlutterFragmentActivity> activityClass,
305 @NonNull String engineGroupId) {
306 this.activityClass = activityClass;
307 this.cachedEngineGroupId = engineGroupId;
308 }
309
310 /**
311 * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded, default to
312 * "main".
313 *
314 * @param dartEntrypoint The dart entrypoint's name
315 * @return The engine group intent builder
316 */
317 @NonNull
318 public NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
319 this.dartEntrypoint = dartEntrypoint;
320 return this;
321 }
322
323 /**
324 * The initial route that a Flutter app will render in this {@code FlutterFragmentActivity},
325 * defaults to "/".
326 */
327 @NonNull
328 public NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute) {
329 this.initialRoute = initialRoute;
330 return this;
331 }
332
333 /**
334 * The mode of {@code FlutterFragmentActivity}'s background, either {@link
335 * BackgroundMode#opaque} or {@link BackgroundMode#transparent}.
336 *
337 * <p>The default background mode is {@link BackgroundMode#opaque}.
338 *
339 * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
340 * {@link FlutterView} of this {@code FlutterFragmentActivity} to be configured with a {@link
341 * FlutterTextureView} to support transparency. This choice has a non-trivial performance
342 * impact. A transparent background should only be used if it is necessary for the app design
343 * being implemented.
344 *
345 * <p>A {@code FlutterFragmentActivity} that is configured with a background mode of {@link
346 * BackgroundMode#transparent} must have a theme applied to it that includes the following
347 * property: {@code <item name="android:windowIsTranslucent">true</item>}.
348 */
349 @NonNull
351 this.backgroundMode = backgroundMode.name();
352 return this;
353 }
354
355 /**
356 * Creates and returns an {@link Intent} that will launch a {@code FlutterFragmentActivity} with
357 * the desired configuration.
358 */
359 @NonNull
360 public Intent build(@NonNull Context context) {
361 return new Intent(context, activityClass)
362 .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
363 .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
364 .putExtra(EXTRA_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId)
365 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
366 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
367 }
368 }
369
370 @Nullable private FlutterFragment flutterFragment;
371
372 @Override
373 protected void onCreate(@Nullable Bundle savedInstanceState) {
374 switchLaunchThemeForNormalTheme();
375 // Get an existing fragment reference first before onCreate since onCreate would re-attach
376 // existing fragments. This would cause FlutterFragment to reference the host activity which
377 // should be aware of its child fragment.
379
380 super.onCreate(savedInstanceState);
381
382 configureWindowForTransparency();
383 setContentView(createFragmentContainer());
384 configureStatusBarForFullscreenFlutterExperience();
385 ensureFlutterFragmentCreated();
386 }
387
388 /**
389 * Switches themes for this {@code Activity} from the theme used to launch this {@code Activity}
390 * to a "normal theme" that is intended for regular {@code Activity} operation.
391 *
392 * <p>This behavior is offered so that a "launch screen" can be displayed while the application
393 * initially loads. To utilize this behavior in an app, do the following:
394 *
395 * <ol>
396 * <li>Create 2 different themes in style.xml: one theme for the launch screen and one theme for
397 * normal display.
398 * <li>In the launch screen theme, set the "windowBackground" property to a {@code Drawable} of
399 * your choice.
400 * <li>In the normal theme, customize however you'd like.
401 * <li>In the AndroidManifest.xml, set the theme of your {@code FlutterFragmentActivity} to your
402 * launch theme.
403 * <li>Add a {@code <meta-data>} property to your {@code FlutterFragmentActivity} with a name of
404 * "io.flutter.embedding.android.NormalTheme" and set the resource to your normal theme,
405 * e.g., {@code android:resource="@style/MyNormalTheme}.
406 * </ol>
407 *
408 * With the above settings, your launch theme will be used when loading the app, and then the
409 * theme will be switched to your normal theme once the app has initialized.
410 *
411 * <p>Do not change aspects of system chrome between a launch theme and normal theme. Either
412 * define both themes to be fullscreen or not, and define both themes to display the same status
413 * bar and navigation bar settings. If you wish to adjust system chrome once your Flutter app
414 * renders, use platform channels to instruct Android to do so at the appropriate time. This will
415 * avoid any jarring visual changes during app startup.
416 */
417 private void switchLaunchThemeForNormalTheme() {
418 try {
419 Bundle metaData = getMetaData();
420 if (metaData != null) {
421 int normalThemeRID = metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
422 if (normalThemeRID != -1) {
423 setTheme(normalThemeRID);
424 }
425 } else {
426 Log.v(TAG, "Using the launch theme as normal theme.");
427 }
428 } catch (PackageManager.NameNotFoundException exception) {
429 Log.e(
430 TAG,
431 "Could not read meta-data for FlutterFragmentActivity. Using the launch theme as normal theme.");
432 }
433 }
434
435 /**
436 * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
437 * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link
438 * BackgroundMode#transparent}.
439 *
440 * <p>For {@code Activity} transparency to work as expected, the theme applied to this {@code
441 * Activity} must include {@code <item name="android:windowIsTranslucent">true</item>}.
442 */
443 private void configureWindowForTransparency() {
444 BackgroundMode backgroundMode = getBackgroundMode();
445 if (backgroundMode == BackgroundMode.transparent) {
446 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
447 }
448 }
449
450 /**
451 * Creates a {@link FrameLayout} with an ID of {@code #FRAGMENT_CONTAINER_ID} that will contain
452 * the {@link FlutterFragment} displayed by this {@code FlutterFragmentActivity}.
453 *
454 * <p>
455 *
456 * @return the FrameLayout container
457 */
458 @NonNull
459 private View createFragmentContainer() {
460 FrameLayout container = provideRootLayout(this);
461 container.setId(FRAGMENT_CONTAINER_ID);
462 container.setLayoutParams(
463 new ViewGroup.LayoutParams(
464 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
465 return container;
466 }
467
468 /**
469 * Retrieves the previously created {@link FlutterFragment} if possible.
470 *
471 * <p>If the activity is recreated, an existing {@link FlutterFragment} may already exist. Retain
472 * a reference to that {@link FlutterFragment} in the {@code #flutterFragment} field and avoid
473 * re-creating another {@link FlutterFragment}.
474 */
475 @VisibleForTesting
477 FragmentManager fragmentManager = getSupportFragmentManager();
478 return (FlutterFragment) fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT);
479 }
480
481 /**
482 * Ensure that a {@link FlutterFragment} is attached to this {@code FlutterFragmentActivity}.
483 *
484 * <p>If no {@link FlutterFragment} exists in this {@code FlutterFragmentActivity}, then a {@link
485 * FlutterFragment} is created and added.
486 */
487 private void ensureFlutterFragmentCreated() {
488 if (flutterFragment == null) {
489 // If both activity and fragment have been destroyed, the activity restore may have
490 // already recreated a new instance of the fragment again via the FragmentActivity.onCreate
491 // and the FragmentManager.
493 }
494 if (flutterFragment == null) {
495 // No FlutterFragment exists yet. This must be the initial Activity creation. We will create
496 // and add a new FlutterFragment to this Activity.
497 flutterFragment = createFlutterFragment();
498 FragmentManager fragmentManager = getSupportFragmentManager();
499 fragmentManager
500 .beginTransaction()
501 .add(FRAGMENT_CONTAINER_ID, flutterFragment, TAG_FLUTTER_FRAGMENT)
502 .commit();
503 }
504 }
505
506 /**
507 * Creates the instance of the {@link FlutterFragment} that this {@code FlutterFragmentActivity}
508 * displays.
509 *
510 * <p>Subclasses may override this method to return a specialization of {@link FlutterFragment}.
511 */
512 @NonNull
514 final BackgroundMode backgroundMode = getBackgroundMode();
515 final RenderMode renderMode = getRenderMode();
516 final TransparencyMode transparencyMode =
517 backgroundMode == BackgroundMode.opaque
518 ? TransparencyMode.opaque
520 final boolean shouldDelayFirstAndroidViewDraw = renderMode == RenderMode.surface;
521 final boolean shouldAutomaticallyHandleOnBackPressed = true;
522
523 if (getCachedEngineId() != null) {
524 Log.v(
525 TAG,
526 "Creating FlutterFragment with cached engine:\n"
527 + "Cached engine ID: "
529 + "\n"
530 + "Will destroy engine when Activity is destroyed: "
532 + "\n"
533 + "Background transparency mode: "
534 + backgroundMode
535 + "\n"
536 + "Will attach FlutterEngine to Activity: "
538
540 .renderMode(renderMode)
541 .transparencyMode(transparencyMode)
542 .handleDeeplinking(shouldHandleDeeplinking())
543 .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
544 .destroyEngineWithFragment(shouldDestroyEngineWithHost())
545 .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
546 .shouldAutomaticallyHandleOnBackPressed(shouldAutomaticallyHandleOnBackPressed)
547 .build();
548 } else {
549 Log.v(
550 TAG,
551 "Creating FlutterFragment with new engine:\n"
552 + "Cached engine group ID: "
554 + "\n"
555 + "Background transparency mode: "
556 + backgroundMode
557 + "\n"
558 + "Dart entrypoint: "
560 + "\n"
561 + "Dart entrypoint library uri: "
563 + "\n"
564 + "Initial route: "
566 + "\n"
567 + "App bundle path: "
569 + "\n"
570 + "Will attach FlutterEngine to Activity: "
572
573 if (getCachedEngineGroupId() != null) {
575 .dartEntrypoint(getDartEntrypointFunctionName())
576 .initialRoute(getInitialRoute())
577 .handleDeeplinking(shouldHandleDeeplinking())
578 .renderMode(renderMode)
579 .transparencyMode(transparencyMode)
580 .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
581 .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
582 .shouldAutomaticallyHandleOnBackPressed(shouldAutomaticallyHandleOnBackPressed)
583 .build();
584 }
585
587 .dartEntrypoint(getDartEntrypointFunctionName())
588 .dartLibraryUri(getDartEntrypointLibraryUri())
589 .dartEntrypointArgs(getDartEntrypointArgs())
590 .initialRoute(getInitialRoute())
591 .appBundlePath(getAppBundlePath())
592 .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
593 .handleDeeplinking(shouldHandleDeeplinking())
594 .renderMode(renderMode)
595 .transparencyMode(transparencyMode)
596 .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
597 .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
598 .shouldAutomaticallyHandleOnBackPressed(shouldAutomaticallyHandleOnBackPressed)
599 .build();
600 }
601 }
602
603 private void configureStatusBarForFullscreenFlutterExperience() {
604 Window window = getWindow();
605 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
606 window.setStatusBarColor(0x40000000);
607 window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
608 }
609
610 @Override
611 public void onPostResume() {
612 super.onPostResume();
613 flutterFragment.onPostResume();
614 }
615
616 @Override
617 protected void onNewIntent(@NonNull Intent intent) {
618 // Forward Intents to our FlutterFragment in case it cares.
619 flutterFragment.onNewIntent(intent);
620 super.onNewIntent(intent);
621 }
622
623 @Override
625 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
626 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
627 flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
628 }
629
630 @Override
631 @SuppressWarnings("MissingSuperCall")
632 public void onUserLeaveHint() {
633 flutterFragment.onUserLeaveHint();
634 }
635
636 @Override
637 public void onTrimMemory(int level) {
638 super.onTrimMemory(level);
639 flutterFragment.onTrimMemory(level);
640 }
641
642 @Override
643 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
644 super.onActivityResult(requestCode, resultCode, data);
645 flutterFragment.onActivityResult(requestCode, resultCode, data);
646 }
647
648 @SuppressWarnings("unused")
649 @Nullable
651 return flutterFragment.getFlutterEngine();
652 }
653
654 /**
655 * Returns false if the {@link io.flutter.embedding.engine.FlutterEngine} backing this {@code
656 * FlutterFragmentActivity} should outlive this {@code FlutterFragmentActivity}, or true to be
657 * destroyed when the {@code FlutterFragmentActivity} is destroyed.
658 *
659 * <p>The default value is {@code true} in cases where {@code FlutterFragmentActivity} created its
660 * own {@link io.flutter.embedding.engine.FlutterEngine}, and {@code false} in cases where a
661 * cached {@link io.flutter.embedding.engine.FlutterEngine} was provided.
662 */
664 return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
665 }
666
667 /**
668 * Hook for subclasses to control whether or not the {@link FlutterFragment} within this {@code
669 * Activity} automatically attaches its {@link io.flutter.embedding.engine.FlutterEngine} to this
670 * {@code Activity}.
671 *
672 * <p>For an explanation of why this control exists, see {@link
673 * FlutterFragment.NewEngineFragmentBuilder#shouldAttachEngineToActivity()}.
674 *
675 * <p>This property is controlled with a protected method instead of an {@code Intent} argument
676 * because the only situation where changing this value would help, is a situation in which {@code
677 * FlutterFragmentActivity} is being subclassed to utilize a custom and/or cached {@link
678 * FlutterEngine}.
679 *
680 * <p>Defaults to {@code true}.
681 */
682 protected boolean shouldAttachEngineToActivity() {
683 return true;
684 }
685
686 /**
687 * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
688 * getInitialRoute} returns null.
689 *
690 * <p>The default implementation looks {@code <meta-data>} called {@link
691 * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest
692 * definition for this {@code FlutterFragmentActivity}.
693 */
694 @VisibleForTesting
695 protected boolean shouldHandleDeeplinking() {
696 try {
697 Bundle metaData = getMetaData();
699 metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false;
701 } catch (PackageManager.NameNotFoundException e) {
702 return false;
703 }
704 }
705
706 /** Hook for subclasses to easily provide a custom {@code FlutterEngine}. */
707 @Nullable
708 @Override
709 public FlutterEngine provideFlutterEngine(@NonNull Context context) {
710 // No-op. Hook for subclasses.
711 return null;
712 }
713
714 /**
715 * Hook for subclasses to easily configure a {@code FlutterEngine}.
716 *
717 * <p>This method is called after {@link #provideFlutterEngine(Context)}.
718 *
719 * <p>All plugins listed in the app's pubspec are registered in the base implementation of this
720 * method unless the FlutterEngine for this activity was externally created. To avoid the
721 * automatic plugin registration for implicitly created FlutterEngines, override this method
722 * without invoking super(). To keep automatic plugin registration and further configure the
723 * FlutterEngine, override this method, invoke super(), and then configure the FlutterEngine as
724 * desired.
725 */
726 @Override
727 public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
728 if (flutterFragment != null && flutterFragment.isFlutterEngineInjected()) {
729 // If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
730 // builder should explicitly decide whether to automatically register plugins via the
731 // FlutterEngine's construction parameter or via the AndroidManifest metadata.
732 return;
733 }
734
736 }
737
738 /**
739 * Hook for the host to cleanup references that were established in {@link
740 * #configureFlutterEngine(FlutterEngine)} before the host is destroyed or detached.
741 *
742 * <p>This method is called in {@link #onDestroy()}.
743 */
744 @Override
745 public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
746 // No-op. Hook for subclasses.
747 }
748
749 /**
750 * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code
751 * snapshots.
752 *
753 * <p>When this {@code FlutterFragmentActivity} is run by Flutter tooling and a data String is
754 * included in the launching {@code Intent}, that data String is interpreted as an app bundle
755 * path.
756 *
757 * <p>When otherwise unspecified, the value is null, which defaults to the app bundle path defined
758 * in {@link io.flutter.embedding.engine.loader.FlutterLoader#findAppBundlePath()}.
759 *
760 * <p>Subclasses may override this method to return a custom app bundle path.
761 */
762 @NonNull
763 protected String getAppBundlePath() {
764 // If this Activity was launched from tooling, and the incoming Intent contains
765 // a custom app bundle path, return that path.
766 // TODO(mattcarroll): determine if we should have an explicit FlutterTestActivity instead of
767 // conflating.
768 if (isDebuggable() && Intent.ACTION_RUN.equals(getIntent().getAction())) {
769 String appBundlePath = getIntent().getDataString();
770 if (appBundlePath != null) {
771 return appBundlePath;
772 }
773 }
774
775 return null;
776 }
777
778 /** Retrieves the meta data specified in the AndroidManifest.xml. */
779 @Nullable
780 protected Bundle getMetaData() throws PackageManager.NameNotFoundException {
781 ActivityInfo activityInfo =
782 getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
783 return activityInfo.metaData;
784 }
785
786 /**
787 * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
788 *
789 * <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
790 * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_META_DATA_KEY} within the Android manifest
791 * definition for this {@code FlutterFragmentActivity}.
792 *
793 * <p>Subclasses may override this method to directly control the Dart entrypoint.
794 */
795 @NonNull
797 try {
798 Bundle metaData = getMetaData();
799 String desiredDartEntrypoint =
800 metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
801 return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
802 } catch (PackageManager.NameNotFoundException e) {
803 return DEFAULT_DART_ENTRYPOINT;
804 }
805 }
806
807 /**
808 * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint function.
809 *
810 * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
811 *
812 * <p>Subclasses may override this method to directly control the Dart entrypoint arguments.
813 */
814 @Nullable
816 return (List<String>) getIntent().getSerializableExtra(EXTRA_DART_ENTRYPOINT_ARGS);
817 }
818
819 /**
820 * The Dart library URI for the entrypoint that will be executed as soon as the Dart snapshot is
821 * loaded.
822 *
823 * <p>Example value: "package:foo/bar.dart"
824 *
825 * <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
826 * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_URI_META_DATA_KEY} within the Android manifest
827 * definition for this {@code FlutterFragmentActivity}.
828 *
829 * <p>A value of null means use the default root library.
830 *
831 * <p>Subclasses may override this method to directly control the Dart entrypoint uri.
832 */
833 @Nullable
835 try {
836 Bundle metaData = getMetaData();
837 String desiredDartLibraryUri =
838 metaData != null ? metaData.getString(DART_ENTRYPOINT_URI_META_DATA_KEY) : null;
839 return desiredDartLibraryUri;
840 } catch (PackageManager.NameNotFoundException e) {
841 return null;
842 }
843 }
844
845 /**
846 * The initial route that a Flutter app will render upon loading and executing its Dart code.
847 *
848 * <p>This preference can be controlled with 2 methods:
849 *
850 * <ol>
851 * <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE} with the
852 * launching {@code Intent}, or
853 * <li>Set a {@code <meta-data>} called {@link
854 * FlutterActivityLaunchConfigs#INITIAL_ROUTE_META_DATA_KEY} for this {@code Activity} in
855 * the Android manifest.
856 * </ol>
857 *
858 * If both preferences are set, the {@code Intent} preference takes priority.
859 *
860 * <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
861 * Activity} might be the very first {@code Activity} launched, which means the developer won't
862 * have control over the incoming {@code Intent}.
863 *
864 * <p>Subclasses may override this method to directly control the initial route.
865 *
866 * <p>If this method returns null and the {@code shouldHandleDeeplinking} returns true, the
867 * initial route is derived from the {@code Intent} through the Intent.getData() instead.
868 */
869 protected String getInitialRoute() {
870 if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
871 return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
872 }
873
874 try {
875 Bundle metaData = getMetaData();
876 String desiredInitialRoute =
877 metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
878 return desiredInitialRoute;
879 } catch (PackageManager.NameNotFoundException e) {
880 return null;
881 }
882 }
883
884 /**
885 * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to use
886 * within this {@code FlutterFragmentActivity}, or {@code null} if this {@code
887 * FlutterFragmentActivity} does not want to use a cached {@link
888 * io.flutter.embedding.engine.FlutterEngine}.
889 */
890 @Nullable
891 protected String getCachedEngineId() {
892 return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
893 }
894
895 @Nullable
896 protected String getCachedEngineGroupId() {
897 return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID);
898 }
899
900 /**
901 * The desired window background mode of this {@code Activity}, which defaults to {@link
902 * BackgroundMode#opaque}.
903 */
904 @NonNull
906 if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
907 return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
908 } else {
909 return BackgroundMode.opaque;
910 }
911 }
912
913 /**
914 * Returns the desired {@link RenderMode} for the {@link FlutterView} displayed in this {@code
915 * FlutterFragmentActivity}.
916 *
917 * <p>That is, {@link RenderMode#surface} if {@link FlutterFragmentActivity#getBackgroundMode()}
918 * is {@link BackgroundMode#opaque} or {@link RenderMode#texture} otherwise.
919 */
920 @NonNull
922 final BackgroundMode backgroundMode = getBackgroundMode();
923 return backgroundMode == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture;
924 }
925
926 /**
927 * Returns true if Flutter is running in "debug mode", and false otherwise.
928 *
929 * <p>Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
930 */
931 private boolean isDebuggable() {
932 return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
933 }
934
935 /** Returns a {@link FrameLayout} that is used as the content view of this activity. */
936 @NonNull
937 protected FrameLayout provideRootLayout(Context context) {
938 return new FrameLayout(context);
939 }
940}
static void v(@NonNull String tag, @NonNull String message)
Definition: Log.java:40
CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity)
CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode)
CachedEngineIntentBuilder( @NonNull Class<? extends FlutterFragmentActivity > activityClass, @NonNull String engineId)
NewEngineInGroupIntentBuilder( @NonNull Class<? extends FlutterFragmentActivity > activityClass, @NonNull String engineGroupId)
NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint)
NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode)
NewEngineIntentBuilder(@NonNull Class<? extends FlutterFragmentActivity > activityClass)
NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode)
NewEngineIntentBuilder dartEntrypointArgs(@Nullable List< String > dartEntrypointArgs)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
void onActivityResult(int requestCode, int resultCode, Intent data)
FlutterEngine provideFlutterEngine(@NonNull Context context)
static Intent createDefaultIntent(@NonNull Context launchContext)
static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId)
static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId)
void onCreate(@Nullable Bundle savedInstanceState)
void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine)
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
static NewEngineFragmentBuilder withNewEngine()
static NewEngineInGroupFragmentBuilder withNewEngineInGroup( @NonNull String engineGroupId)
static CachedEngineFragmentBuilder withCachedEngine(@NonNull String engineId)
static FlutterShellArgs fromIntent(@NonNull Intent intent)
static void registerGeneratedPlugins(@NonNull FlutterEngine flutterEngine)
GLFWwindow * window
Definition: main.cc:45
FlutterSemanticsFlag flags
SK_API sk_sp< SkShader > Color(SkColor)
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
#define TAG()
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63