Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
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
246 public CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
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
350 public NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
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
522 if (getCachedEngineId() != null) {
523 Log.v(
524 TAG,
525 "Creating FlutterFragment with cached engine:\n"
526 + "Cached engine ID: "
528 + "\n"
529 + "Will destroy engine when Activity is destroyed: "
531 + "\n"
532 + "Background transparency mode: "
533 + backgroundMode
534 + "\n"
535 + "Will attach FlutterEngine to Activity: "
537
539 .renderMode(renderMode)
540 .transparencyMode(transparencyMode)
541 .handleDeeplinking(shouldHandleDeeplinking())
542 .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
543 .destroyEngineWithFragment(shouldDestroyEngineWithHost())
544 .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
545 .build();
546 } else {
547 Log.v(
548 TAG,
549 "Creating FlutterFragment with new engine:\n"
550 + "Cached engine group ID: "
552 + "\n"
553 + "Background transparency mode: "
554 + backgroundMode
555 + "\n"
556 + "Dart entrypoint: "
558 + "\n"
559 + "Dart entrypoint library uri: "
561 + "\n"
562 + "Initial route: "
564 + "\n"
565 + "App bundle path: "
567 + "\n"
568 + "Will attach FlutterEngine to Activity: "
570
571 if (getCachedEngineGroupId() != null) {
573 .dartEntrypoint(getDartEntrypointFunctionName())
574 .initialRoute(getInitialRoute())
575 .handleDeeplinking(shouldHandleDeeplinking())
576 .renderMode(renderMode)
577 .transparencyMode(transparencyMode)
578 .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
579 .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
580 .build();
581 }
582
584 .dartEntrypoint(getDartEntrypointFunctionName())
585 .dartLibraryUri(getDartEntrypointLibraryUri())
586 .dartEntrypointArgs(getDartEntrypointArgs())
587 .initialRoute(getInitialRoute())
588 .appBundlePath(getAppBundlePath())
589 .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
590 .handleDeeplinking(shouldHandleDeeplinking())
591 .renderMode(renderMode)
592 .transparencyMode(transparencyMode)
593 .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
594 .shouldDelayFirstAndroidViewDraw(shouldDelayFirstAndroidViewDraw)
595 .build();
596 }
597 }
598
599 private void configureStatusBarForFullscreenFlutterExperience() {
600 Window window = getWindow();
601 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
602 window.setStatusBarColor(0x40000000);
603 window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
604 }
605
606 @Override
607 public void onPostResume() {
608 super.onPostResume();
609 flutterFragment.onPostResume();
610 }
611
612 @Override
613 protected void onNewIntent(@NonNull Intent intent) {
614 // Forward Intents to our FlutterFragment in case it cares.
615 flutterFragment.onNewIntent(intent);
616 super.onNewIntent(intent);
617 }
618
619 @Override
620 @SuppressWarnings("MissingSuperCall")
621 public void onBackPressed() {
622 flutterFragment.onBackPressed();
623 }
624
625 @Override
627 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
628 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
629 flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
630 }
631
632 @Override
633 @SuppressWarnings("MissingSuperCall")
634 public void onUserLeaveHint() {
635 flutterFragment.onUserLeaveHint();
636 }
637
638 @Override
639 public void onTrimMemory(int level) {
640 super.onTrimMemory(level);
641 flutterFragment.onTrimMemory(level);
642 }
643
644 @Override
645 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
646 super.onActivityResult(requestCode, resultCode, data);
647 flutterFragment.onActivityResult(requestCode, resultCode, data);
648 }
649
650 @SuppressWarnings("unused")
651 @Nullable
653 return flutterFragment.getFlutterEngine();
654 }
655
656 /**
657 * Returns false if the {@link io.flutter.embedding.engine.FlutterEngine} backing this {@code
658 * FlutterFragmentActivity} should outlive this {@code FlutterFragmentActivity}, or true to be
659 * destroyed when the {@code FlutterFragmentActivity} is destroyed.
660 *
661 * <p>The default value is {@code true} in cases where {@code FlutterFragmentActivity} created its
662 * own {@link io.flutter.embedding.engine.FlutterEngine}, and {@code false} in cases where a
663 * cached {@link io.flutter.embedding.engine.FlutterEngine} was provided.
664 */
666 return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
667 }
668
669 /**
670 * Hook for subclasses to control whether or not the {@link FlutterFragment} within this {@code
671 * Activity} automatically attaches its {@link io.flutter.embedding.engine.FlutterEngine} to this
672 * {@code Activity}.
673 *
674 * <p>For an explanation of why this control exists, see {@link
675 * FlutterFragment.NewEngineFragmentBuilder#shouldAttachEngineToActivity()}.
676 *
677 * <p>This property is controlled with a protected method instead of an {@code Intent} argument
678 * because the only situation where changing this value would help, is a situation in which {@code
679 * FlutterFragmentActivity} is being subclassed to utilize a custom and/or cached {@link
680 * FlutterEngine}.
681 *
682 * <p>Defaults to {@code true}.
683 */
684 protected boolean shouldAttachEngineToActivity() {
685 return true;
686 }
687
688 /**
689 * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
690 * getInitialRoute} returns null.
691 *
692 * <p>The default implementation looks {@code <meta-data>} called {@link
693 * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest
694 * definition for this {@code FlutterFragmentActivity}.
695 */
696 @VisibleForTesting
697 protected boolean shouldHandleDeeplinking() {
698 try {
699 Bundle metaData = getMetaData();
701 metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false;
703 } catch (PackageManager.NameNotFoundException e) {
704 return false;
705 }
706 }
707
708 /** Hook for subclasses to easily provide a custom {@code FlutterEngine}. */
709 @Nullable
710 @Override
711 public FlutterEngine provideFlutterEngine(@NonNull Context context) {
712 // No-op. Hook for subclasses.
713 return null;
714 }
715
716 /**
717 * Hook for subclasses to easily configure a {@code FlutterEngine}.
718 *
719 * <p>This method is called after {@link #provideFlutterEngine(Context)}.
720 *
721 * <p>All plugins listed in the app's pubspec are registered in the base implementation of this
722 * method unless the FlutterEngine for this activity was externally created. To avoid the
723 * automatic plugin registration for implicitly created FlutterEngines, override this method
724 * without invoking super(). To keep automatic plugin registration and further configure the
725 * FlutterEngine, override this method, invoke super(), and then configure the FlutterEngine as
726 * desired.
727 */
728 @Override
729 public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
730 if (flutterFragment != null && flutterFragment.isFlutterEngineInjected()) {
731 // If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
732 // builder should explicitly decide whether to automatically register plugins via the
733 // FlutterEngine's construction parameter or via the AndroidManifest metadata.
734 return;
735 }
736
737 GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
738 }
739
740 /**
741 * Hook for the host to cleanup references that were established in {@link
742 * #configureFlutterEngine(FlutterEngine)} before the host is destroyed or detached.
743 *
744 * <p>This method is called in {@link #onDestroy()}.
745 */
746 @Override
747 public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
748 // No-op. Hook for subclasses.
749 }
750
751 /**
752 * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code
753 * snapshots.
754 *
755 * <p>When this {@code FlutterFragmentActivity} is run by Flutter tooling and a data String is
756 * included in the launching {@code Intent}, that data String is interpreted as an app bundle
757 * path.
758 *
759 * <p>When otherwise unspecified, the value is null, which defaults to the app bundle path defined
760 * in {@link io.flutter.embedding.engine.loader.FlutterLoader#findAppBundlePath()}.
761 *
762 * <p>Subclasses may override this method to return a custom app bundle path.
763 */
764 @NonNull
765 protected String getAppBundlePath() {
766 // If this Activity was launched from tooling, and the incoming Intent contains
767 // a custom app bundle path, return that path.
768 // TODO(mattcarroll): determine if we should have an explicit FlutterTestActivity instead of
769 // conflating.
770 if (isDebuggable() && Intent.ACTION_RUN.equals(getIntent().getAction())) {
771 String appBundlePath = getIntent().getDataString();
772 if (appBundlePath != null) {
773 return appBundlePath;
774 }
775 }
776
777 return null;
778 }
779
780 /** Retrieves the meta data specified in the AndroidManifest.xml. */
781 @Nullable
782 protected Bundle getMetaData() throws PackageManager.NameNotFoundException {
783 ActivityInfo activityInfo =
784 getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
785 return activityInfo.metaData;
786 }
787
788 /**
789 * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
790 *
791 * <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
792 * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_META_DATA_KEY} within the Android manifest
793 * definition for this {@code FlutterFragmentActivity}.
794 *
795 * <p>Subclasses may override this method to directly control the Dart entrypoint.
796 */
797 @NonNull
799 try {
800 Bundle metaData = getMetaData();
801 String desiredDartEntrypoint =
802 metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
803 return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
804 } catch (PackageManager.NameNotFoundException e) {
805 return DEFAULT_DART_ENTRYPOINT;
806 }
807 }
808
809 /**
810 * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint function.
811 *
812 * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
813 *
814 * <p>Subclasses may override this method to directly control the Dart entrypoint arguments.
815 */
816 @Nullable
818 return (List<String>) getIntent().getSerializableExtra(EXTRA_DART_ENTRYPOINT_ARGS);
819 }
820
821 /**
822 * The Dart library URI for the entrypoint that will be executed as soon as the Dart snapshot is
823 * loaded.
824 *
825 * <p>Example value: "package:foo/bar.dart"
826 *
827 * <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
828 * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_URI_META_DATA_KEY} within the Android manifest
829 * definition for this {@code FlutterFragmentActivity}.
830 *
831 * <p>A value of null means use the default root library.
832 *
833 * <p>Subclasses may override this method to directly control the Dart entrypoint uri.
834 */
835 @Nullable
837 try {
838 Bundle metaData = getMetaData();
839 String desiredDartLibraryUri =
840 metaData != null ? metaData.getString(DART_ENTRYPOINT_URI_META_DATA_KEY) : null;
841 return desiredDartLibraryUri;
842 } catch (PackageManager.NameNotFoundException e) {
843 return null;
844 }
845 }
846
847 /**
848 * The initial route that a Flutter app will render upon loading and executing its Dart code.
849 *
850 * <p>This preference can be controlled with 2 methods:
851 *
852 * <ol>
853 * <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE} with the
854 * launching {@code Intent}, or
855 * <li>Set a {@code <meta-data>} called {@link
856 * FlutterActivityLaunchConfigs#INITIAL_ROUTE_META_DATA_KEY} for this {@code Activity} in
857 * the Android manifest.
858 * </ol>
859 *
860 * If both preferences are set, the {@code Intent} preference takes priority.
861 *
862 * <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
863 * Activity} might be the very first {@code Activity} launched, which means the developer won't
864 * have control over the incoming {@code Intent}.
865 *
866 * <p>Subclasses may override this method to directly control the initial route.
867 *
868 * <p>If this method returns null and the {@code shouldHandleDeeplinking} returns true, the
869 * initial route is derived from the {@code Intent} through the Intent.getData() instead.
870 */
871 protected String getInitialRoute() {
872 if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
873 return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
874 }
875
876 try {
877 Bundle metaData = getMetaData();
878 String desiredInitialRoute =
879 metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
880 return desiredInitialRoute;
881 } catch (PackageManager.NameNotFoundException e) {
882 return null;
883 }
884 }
885
886 /**
887 * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to use
888 * within this {@code FlutterFragmentActivity}, or {@code null} if this {@code
889 * FlutterFragmentActivity} does not want to use a cached {@link
890 * io.flutter.embedding.engine.FlutterEngine}.
891 */
892 @Nullable
893 protected String getCachedEngineId() {
894 return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
895 }
896
897 @Nullable
898 protected String getCachedEngineGroupId() {
899 return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID);
900 }
901
902 /**
903 * The desired window background mode of this {@code Activity}, which defaults to {@link
904 * BackgroundMode#opaque}.
905 */
906 @NonNull
907 protected BackgroundMode getBackgroundMode() {
908 if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
909 return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
910 } else {
911 return BackgroundMode.opaque;
912 }
913 }
914
915 /**
916 * Returns the desired {@link RenderMode} for the {@link FlutterView} displayed in this {@code
917 * FlutterFragmentActivity}.
918 *
919 * <p>That is, {@link RenderMode#surface} if {@link FlutterFragmentActivity#getBackgroundMode()}
920 * is {@link BackgroundMode#opaque} or {@link RenderMode#texture} otherwise.
921 */
922 @NonNull
924 final BackgroundMode backgroundMode = getBackgroundMode();
925 return backgroundMode == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture;
926 }
927
928 /**
929 * Returns true if Flutter is running in "debug mode", and false otherwise.
930 *
931 * <p>Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
932 */
933 private boolean isDebuggable() {
934 return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
935 }
936
937 /** Returns a {@link FrameLayout} that is used as the content view of this activity. */
938 @NonNull
939 protected FrameLayout provideRootLayout(Context context) {
940 return new FrameLayout(context);
941 }
942}
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 cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine)
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
static NewEngineFragmentBuilder withNewEngine()
static NewEngineInGroupFragmentBuilder withNewEngineInGroup( @NonNull String engineGroupId)
static CachedEngineFragmentBuilder withCachedEngine(@NonNull String engineId)
GLFWwindow * window
Definition main.cc:45
FlutterSemanticsFlag flags
#define TAG()