Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterActivity.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.Build.API_LEVELS;
8import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY;
9import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_URI_META_DATA_KEY;
10import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE;
11import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
12import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
13import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
14import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID;
15import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
16import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT;
17import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS;
18import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
19import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_ENABLE_STATE_RESTORATION;
20import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
21import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
22import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
23import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;
24
25import android.annotation.TargetApi;
26import android.app.Activity;
27import android.content.Context;
28import android.content.Intent;
29import android.content.pm.ActivityInfo;
30import android.content.pm.ApplicationInfo;
31import android.content.pm.PackageManager;
32import android.graphics.Color;
33import android.graphics.drawable.ColorDrawable;
34import android.os.Build;
35import android.os.Bundle;
36import android.view.View;
37import android.view.Window;
38import android.view.WindowManager;
39import android.window.BackEvent;
40import android.window.OnBackAnimationCallback;
41import android.window.OnBackInvokedCallback;
42import android.window.OnBackInvokedDispatcher;
43import androidx.annotation.NonNull;
44import androidx.annotation.Nullable;
45import androidx.annotation.RequiresApi;
46import androidx.annotation.VisibleForTesting;
47import androidx.lifecycle.Lifecycle;
48import androidx.lifecycle.LifecycleOwner;
49import androidx.lifecycle.LifecycleRegistry;
50import io.flutter.Log;
51import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
52import io.flutter.embedding.engine.FlutterEngine;
53import io.flutter.embedding.engine.FlutterShellArgs;
54import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
55import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
56import io.flutter.plugin.platform.PlatformPlugin;
57import java.util.ArrayList;
58import java.util.List;
59
60/**
61 * {@code Activity} which displays a fullscreen Flutter UI.
62 *
63 * <p>{@code FlutterActivity} is the simplest and most direct way to integrate Flutter within an
64 * Android app.
65 *
66 * <p><strong>FlutterActivity responsibilities</strong>
67 *
68 * <p>{@code FlutterActivity} maintains the following responsibilities:
69 *
70 * <ul>
71 * <li>Displays an Android launch screen.
72 * <li>Configures the status bar appearance.
73 * <li>Chooses the Dart execution app bundle path, entrypoint and entrypoint arguments.
74 * <li>Chooses Flutter's initial route.
75 * <li>Renders {@code Activity} transparently, if desired.
76 * <li>Offers hooks for subclasses to provide and configure a {@link
77 * io.flutter.embedding.engine.FlutterEngine}.
78 * <li>Save and restore instance state, see {@code #shouldRestoreAndSaveState()};
79 * </ul>
80 *
81 * <p><strong>Dart entrypoint, entrypoint arguments, initial route, and app bundle path</strong>
82 *
83 * <p>The Dart entrypoint executed within this {@code Activity} is "main()" by default. To change
84 * the entrypoint that a {@code FlutterActivity} executes, subclass {@code FlutterActivity} and
85 * override {@link #getDartEntrypointFunctionName()}. For non-main Dart entrypoints to not be
86 * tree-shaken away, you need to annotate those functions with {@code @pragma('vm:entry-point')} in
87 * Dart.
88 *
89 * <p>The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint
90 * function. It can be passed using a {@link NewEngineIntentBuilder} via {@link
91 * NewEngineIntentBuilder#dartEntrypointArgs}.
92 *
93 * <p>The Flutter route that is initially loaded within this {@code Activity} is "/". The initial
94 * route may be specified explicitly by passing the name of the route as a {@code String} in {@link
95 * FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE}, e.g., "my/deep/link".
96 *
97 * <p>The initial route can each be controlled using a {@link NewEngineIntentBuilder} via {@link
98 * NewEngineIntentBuilder#initialRoute}.
99 *
100 * <p>The app bundle path, Dart entrypoint, Dart entrypoint arguments, and initial route can also be
101 * controlled in a subclass of {@code FlutterActivity} by overriding their respective methods:
102 *
103 * <ul>
104 * <li>{@link #getAppBundlePath()}
105 * <li>{@link #getDartEntrypointFunctionName()}
106 * <li>{@link #getDartEntrypointArgs()}
107 * <li>{@link #getInitialRoute()}
108 * </ul>
109 *
110 * <p>The Dart entrypoint and app bundle path are not supported as {@code Intent} parameters since
111 * your Dart library entrypoints are your private APIs and Intents are invocable by other processes.
112 *
113 * <p><strong>Using a cached FlutterEngine</strong>
114 *
115 * <p>{@code FlutterActivity} can be used with a cached {@link
116 * io.flutter.embedding.engine.FlutterEngine} instead of creating a new one. Use {@link
117 * #withCachedEngine(String)} to build a {@code FlutterActivity} {@code Intent} that is configured
118 * to use an existing, cached {@link io.flutter.embedding.engine.FlutterEngine}. {@link
119 * io.flutter.embedding.engine.FlutterEngineCache} is the cache that is used to obtain a given
120 * cached {@link io.flutter.embedding.engine.FlutterEngine}. You must create and put a {@link
121 * io.flutter.embedding.engine.FlutterEngine} into the {@link
122 * io.flutter.embedding.engine.FlutterEngineCache} yourself before using the {@link
123 * #withCachedEngine(String)} builder. An {@code IllegalStateException} will be thrown if a cached
124 * engine is requested but does not exist in the cache.
125 *
126 * <p>When using a cached {@link io.flutter.embedding.engine.FlutterEngine}, that {@link
127 * io.flutter.embedding.engine.FlutterEngine} should already be executing Dart code, which means
128 * that the Dart entrypoint and initial route have already been defined. Therefore, {@link
129 * CachedEngineIntentBuilder} does not offer configuration of these properties.
130 *
131 * <p>It is generally recommended to use a cached {@link io.flutter.embedding.engine.FlutterEngine}
132 * to avoid a momentary delay when initializing a new {@link
133 * io.flutter.embedding.engine.FlutterEngine}. The two exceptions to using a cached {@link
134 * FlutterEngine} are:
135 *
136 * <ul>
137 * <li>When {@code FlutterActivity} is the first {@code Activity} displayed by the app, because
138 * pre-warming a {@link io.flutter.embedding.engine.FlutterEngine} would have no impact in
139 * this situation.
140 * <li>When you are unsure when/if you will need to display a Flutter experience.
141 * </ul>
142 *
143 * <p>See https://flutter.dev/docs/development/add-to-app/performance for additional performance
144 * explorations on engine loading.
145 *
146 * <p>The following illustrates how to pre-warm and cache a {@link
147 * io.flutter.embedding.engine.FlutterEngine}:
148 *
149 * <pre>{@code
150 * // Create and pre-warm a FlutterEngine.
151 * FlutterEngineGroup group = new FlutterEngineGroup(context);
152 * FlutterEngine flutterEngine = group.createAndRunDefaultEngine(context);
153 * flutterEngine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault());
154 *
155 * // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.
156 * FlutterEngineCache.getInstance().put("my_engine", flutterEngine);
157 * }</pre>
158 *
159 * <p><strong>Alternatives to FlutterActivity</strong>
160 *
161 * <p>If Flutter is needed in a location that cannot use an {@code Activity}, consider using a
162 * {@link FlutterFragment}. Using a {@link FlutterFragment} requires forwarding some calls from an
163 * {@code Activity} to the {@link FlutterFragment}.
164 *
165 * <p>If Flutter is needed in a location that can only use a {@code View}, consider using a {@link
166 * FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an {@code
167 * Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a {@code Fragment}.
168 *
169 * <p><strong>Launch Screen</strong>
170 *
171 * <p>{@code FlutterActivity} supports the display of an Android "launch screen", which is displayed
172 * while the Android application loads. It is only applicable if {@code FlutterActivity} is the
173 * first {@code Activity} displayed upon loading the app.
174 *
175 * <p>Prior to Flutter 2.5, {@code FlutterActivity} supported the display of a Flutter-specific
176 * "splash screen" that would be displayed after the launch screen passes. This has since been
177 * deprecated. If a launch screen is specified, it will automatically persist for as long as it
178 * takes Flutter to initialize and render its first frame.
179 *
180 * <p>Use Android themes to display a launch screen. Create two themes: a launch theme and a normal
181 * theme. In the launch theme, set {@code windowBackground} to the desired {@code Drawable} for the
182 * launch screen. In the normal theme, set {@code windowBackground} to any desired background color
183 * that should normally appear behind your Flutter content. In most cases this background color will
184 * never be seen, but for possible transition edge cases it is a good idea to explicitly replace the
185 * launch screen window background with a neutral color.
186 *
187 * <p>Do not change aspects of system chrome between a launch theme and normal theme. Either define
188 * both themes to be fullscreen or not, and define both themes to display the same status bar and
189 * navigation bar settings. To adjust system chrome once the Flutter app renders, use platform
190 * channels to instruct Android to do so at the appropriate time. This will avoid any jarring visual
191 * changes during app startup.
192 *
193 * <p>In the AndroidManifest.xml, set the theme of {@code FlutterActivity} to the defined launch
194 * theme. In the metadata section for {@code FlutterActivity}, defined the following reference to
195 * your normal theme:
196 *
197 * <p>{@code <meta-data android:name="io.flutter.embedding.android.NormalTheme"
198 * android:resource="@style/YourNormalTheme" /> }
199 *
200 * <p>With themes defined, and AndroidManifest.xml updated, Flutter displays the specified launch
201 * screen until the Android application is initialized.
202 *
203 * <p><strong>Alternative Activity</strong> {@link FlutterFragmentActivity} is also available, which
204 * is similar to {@code FlutterActivity} but it extends {@code FragmentActivity}. You should use
205 * {@code FlutterActivity}, if possible, but if you need a {@code FragmentActivity} then you should
206 * use {@link FlutterFragmentActivity}.
207 */
208// A number of methods in this class have the same implementation as FlutterFragmentActivity. These
209// methods are duplicated for readability purposes. Be sure to replicate any change in this class in
210// FlutterFragmentActivity, too.
211public class FlutterActivity extends Activity
212 implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner {
213 private static final String TAG = "FlutterActivity";
214
215 private boolean hasRegisteredBackCallback = false;
216
217 /**
218 * The ID of the {@code FlutterView} created by this activity.
219 *
220 * <p>This ID can be used to lookup {@code FlutterView} in the Android view hierarchy. For more,
221 * see {@link android.view.View#findViewById}.
222 */
223 public static final int FLUTTER_VIEW_ID = View.generateViewId();
224
225 /**
226 * Creates an {@link Intent} that launches a {@code FlutterActivity}, which creates a {@link
227 * FlutterEngine} that executes a {@code main()} Dart entrypoint, and displays the "/" route as
228 * Flutter's initial route.
229 *
230 * <p>Consider using the {@link #withCachedEngine(String)} {@link Intent} builder to control when
231 * the {@link io.flutter.embedding.engine.FlutterEngine} should be created in your application.
232 *
233 * @param launchContext The launch context. e.g. An Activity.
234 * @return The default intent.
235 */
236 @NonNull
237 public static Intent createDefaultIntent(@NonNull Context launchContext) {
238 return withNewEngine().build(launchContext);
239 }
240
241 /**
242 * Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
243 * launch a {@code FlutterActivity} that internally creates a new {@link
244 * io.flutter.embedding.engine.FlutterEngine} using the desired Dart entrypoint, initial route,
245 * etc.
246 *
247 * @return The engine intent builder.
248 */
249 @NonNull
251 return new NewEngineIntentBuilder(FlutterActivity.class);
252 }
253
254 /**
255 * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new {@link
256 * FlutterEngine} and the desired configuration.
257 */
258 public static class NewEngineIntentBuilder {
259 private final Class<? extends FlutterActivity> activityClass;
260 private String initialRoute = DEFAULT_INITIAL_ROUTE;
261 private String backgroundMode = DEFAULT_BACKGROUND_MODE;
262 @Nullable private List<String> dartEntrypointArgs;
263
264 /**
265 * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
266 * {@code FlutterActivity}.
267 *
268 * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
269 * #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder} constructed
270 * with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
271 *
272 * <p>{@code return new NewEngineIntentBuilder(MyFlutterActivity.class); }
273 */
274 public NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
275 this.activityClass = activityClass;
276 }
277
278 /**
279 * The initial route that a Flutter app will render in this {@link FlutterActivity}, defaults to
280 * "/".
281 *
282 * @param initialRoute The route.
283 * @return The engine intent builder.
284 */
285 @NonNull
286 public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
287 this.initialRoute = initialRoute;
288 return this;
289 }
290
291 /**
292 * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
293 * {@link BackgroundMode#transparent}.
294 *
295 * <p>The default background mode is {@link BackgroundMode#opaque}.
296 *
297 * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
298 * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
299 * FlutterTextureView} to support transparency. This choice has a non-trivial performance
300 * impact. A transparent background should only be used if it is necessary for the app design
301 * being implemented.
302 *
303 * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
304 * BackgroundMode#transparent} must have a theme applied to it that includes the following
305 * property: {@code <item name="android:windowIsTranslucent">true</item>}.
306 *
307 * @param backgroundMode The background mode.
308 * @return The engine intent builder.
309 */
310 @NonNull
311 public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
312 this.backgroundMode = backgroundMode.name();
313 return this;
314 }
315
316 /**
317 * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint
318 * function.
319 *
320 * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
321 *
322 * @param dartEntrypointArgs The Dart entrypoint arguments.
323 * @return The engine intent builder.
324 */
325 @NonNull
326 public NewEngineIntentBuilder dartEntrypointArgs(@Nullable List<String> dartEntrypointArgs) {
327 this.dartEntrypointArgs = dartEntrypointArgs;
328 return this;
329 }
330
331 /**
332 * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
333 * desired configuration.
334 *
335 * @param context The context. e.g. An Activity.
336 * @return The intent.
337 */
338 @NonNull
339 public Intent build(@NonNull Context context) {
340 Intent intent =
341 new Intent(context, activityClass)
342 .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
343 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
344 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
345 if (dartEntrypointArgs != null) {
346 intent.putExtra(EXTRA_DART_ENTRYPOINT_ARGS, new ArrayList(dartEntrypointArgs));
347 }
348 return intent;
349 }
350 }
351
352 /**
353 * Creates a {@link CachedEngineIntentBuilder}, which can be used to configure an {@link Intent}
354 * to launch a {@code FlutterActivity} that internally uses an existing {@link
355 * io.flutter.embedding.engine.FlutterEngine} that is cached in {@link
356 * io.flutter.embedding.engine.FlutterEngineCache}.
357 *
358 * @param cachedEngineId A cached engine ID.
359 * @return The builder.
360 */
361 public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
362 return new CachedEngineIntentBuilder(FlutterActivity.class, cachedEngineId);
363 }
364
365 /**
366 * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with an existing
367 * {@link io.flutter.embedding.engine.FlutterEngine} that is cached in {@link
368 * io.flutter.embedding.engine.FlutterEngineCache}.
369 */
370 public static class CachedEngineIntentBuilder {
371 private final Class<? extends FlutterActivity> activityClass;
372 private final String cachedEngineId;
373 private boolean destroyEngineWithActivity = false;
374 private String backgroundMode = DEFAULT_BACKGROUND_MODE;
375
376 /**
377 * Constructor that allows this {@code CachedEngineIntentBuilder} to be used by subclasses of
378 * {@code FlutterActivity}.
379 *
380 * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
381 * FlutterActivity#withCachedEngine(String)}, which returns an instance of {@code
382 * CachedEngineIntentBuilder} constructed with a {@code Class} reference to the {@code
383 * FlutterActivity} subclass, e.g.:
384 *
385 * <p>{@code return new CachedEngineIntentBuilder(MyFlutterActivity.class, engineId); }
386 *
387 * @param activityClass A subclass of {@code FlutterActivity}.
388 * @param engineId The engine id.
389 */
391 @NonNull Class<? extends FlutterActivity> activityClass, @NonNull String engineId) {
392 this.activityClass = activityClass;
393 this.cachedEngineId = engineId;
394 }
395
396 /**
397 * Whether the cached {@link io.flutter.embedding.engine.FlutterEngine} should be destroyed and
398 * removed from the cache when this {@code FlutterActivity} is destroyed.
399 *
400 * <p>The default value is {@code false}.
401 *
402 * @param destroyEngineWithActivity Whether to destroy the engine.
403 * @return The builder.
404 */
405 public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
406 this.destroyEngineWithActivity = destroyEngineWithActivity;
407 return this;
408 }
409
410 /**
411 * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
412 * {@link BackgroundMode#transparent}.
413 *
414 * <p>The default background mode is {@link BackgroundMode#opaque}.
415 *
416 * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
417 * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
418 * FlutterTextureView} to support transparency. This choice has a non-trivial performance
419 * impact. A transparent background should only be used if it is necessary for the app design
420 * being implemented.
421 *
422 * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
423 * BackgroundMode#transparent} must have a theme applied to it that includes the following
424 * property: {@code <item name="android:windowIsTranslucent">true</item>}.
425 *
426 * @param backgroundMode The background mode
427 * @return The builder.
428 */
429 @NonNull
430 public CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
431 this.backgroundMode = backgroundMode.name();
432 return this;
433 }
434
435 /**
436 * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
437 * desired configuration.
438 *
439 * @param context The context. e.g. An Activity.
440 * @return The intent.
441 */
442 @NonNull
443 public Intent build(@NonNull Context context) {
444 return new Intent(context, activityClass)
445 .putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
446 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
447 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
448 }
449 }
450
451 /**
452 * Creates a {@link NewEngineInGroupIntentBuilder}, which can be used to configure an {@link
453 * Intent} to launch a {@code FlutterActivity} by internally creating a FlutterEngine from an
454 * existing {@link io.flutter.embedding.engine.FlutterEngineGroup} cached in a specified {@link
455 * io.flutter.embedding.engine.FlutterEngineGroupCache}.
456 *
457 * <pre>{@code
458 * // Create a FlutterEngineGroup, such as in the onCreate method of the Application.
459 * FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
460 * FlutterEngineGroupCache.getInstance().put("my_cached_engine_group_id", engineGroup);
461 *
462 * // Start a FlutterActivity with the FlutterEngineGroup by creating an intent with withNewEngineInGroup
463 * Intent intent = FlutterActivity.withNewEngineInGroup("my_cached_engine_group_id")
464 * .dartEntrypoint("custom_entrypoint")
465 * .initialRoute("/custom/route")
466 * .backgroundMode(BackgroundMode.transparent)
467 * .build(context);
468 * startActivity(intent);
469 * }</pre>
470 *
471 * @param engineGroupId A cached engine group ID.
472 * @return The builder.
473 */
474 public static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId) {
475 return new NewEngineInGroupIntentBuilder(FlutterActivity.class, engineGroupId);
476 }
477
478 /**
479 * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new {@link
480 * FlutterEngine} created by FlutterEngineGroup#createAndRunEngine.
481 */
482 public static class NewEngineInGroupIntentBuilder {
483 private final Class<? extends FlutterActivity> activityClass;
484 private final String cachedEngineGroupId;
485 private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
486 private String initialRoute = DEFAULT_INITIAL_ROUTE;
487 private String backgroundMode = DEFAULT_BACKGROUND_MODE;
488
489 /**
490 * Constructor that allows this {@code NewEngineInGroupIntentBuilder} to be used by subclasses
491 * of {@code FlutterActivity}.
492 *
493 * <p>Subclasses of {@code FlutterActivity} should provide their own static version of {@link
494 * #withNewEngineInGroup}, which returns an instance of {@code NewEngineInGroupIntentBuilder}
495 * constructed with a {@code Class} reference to the {@code FlutterActivity} subclass, e.g.:
496 *
497 * <p>{@code return new NewEngineInGroupIntentBuilder(MyFlutterActivity.class,
498 * cacheedEngineGroupId); }
499 *
500 * <pre>{@code
501 * // Create a FlutterEngineGroup, such as in the onCreate method of the Application.
502 * FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
503 * FlutterEngineGroupCache.getInstance().put("my_cached_engine_group_id", engineGroup);
504 *
505 * // Create a NewEngineInGroupIntentBuilder that would build an intent to start my custom FlutterActivity subclass.
506 * FlutterActivity.NewEngineInGroupIntentBuilder intentBuilder =
507 * new FlutterActivity.NewEngineInGroupIntentBuilder(
508 * MyFlutterActivity.class,
509 * app.engineGroupId);
510 * intentBuilder.dartEntrypoint("main")
511 * .initialRoute("/custom/route")
512 * .backgroundMode(BackgroundMode.transparent);
513 * startActivity(intentBuilder.build(context));
514 * }</pre>
515 *
516 * @param activityClass A subclass of {@code FlutterActivity}.
517 * @param engineGroupId The engine group id.
518 */
520 @NonNull Class<? extends FlutterActivity> activityClass, @NonNull String engineGroupId) {
521 this.activityClass = activityClass;
522 this.cachedEngineGroupId = engineGroupId;
523 }
524
525 /**
526 * The Dart entrypoint that will be executed in the newly created FlutterEngine as soon as the
527 * Dart snapshot is loaded. Default to "main".
528 *
529 * @param dartEntrypoint The dart entrypoint's name
530 * @return The engine group intent builder
531 */
532 @NonNull
533 public NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
534 this.dartEntrypoint = dartEntrypoint;
535 return this;
536 }
537
538 /**
539 * The initial route that a Flutter app will render in this {@link FlutterActivity}, defaults to
540 * "/".
541 *
542 * @param initialRoute The route.
543 * @return The engine group intent builder.
544 */
545 @NonNull
546 public NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute) {
547 this.initialRoute = initialRoute;
548 return this;
549 }
550
551 /**
552 * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
553 * {@link BackgroundMode#transparent}.
554 *
555 * <p>The default background mode is {@link BackgroundMode#opaque}.
556 *
557 * <p>Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
558 * {@link FlutterView} of this {@code FlutterActivity} to be configured with a {@link
559 * FlutterTextureView} to support transparency. This choice has a non-trivial performance
560 * impact. A transparent background should only be used if it is necessary for the app design
561 * being implemented.
562 *
563 * <p>A {@code FlutterActivity} that is configured with a background mode of {@link
564 * BackgroundMode#transparent} must have a theme applied to it that includes the following
565 * property: {@code <item name="android:windowIsTranslucent">true</item>}.
566 *
567 * @param backgroundMode The background mode.
568 * @return The engine group intent builder.
569 */
570 @NonNull
571 public NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
572 this.backgroundMode = backgroundMode.name();
573 return this;
574 }
575
576 /**
577 * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with the
578 * desired configuration.
579 *
580 * @param context The context. e.g. An Activity.
581 * @return The intent.
582 */
583 @NonNull
584 public Intent build(@NonNull Context context) {
585 return new Intent(context, activityClass)
586 .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
587 .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
588 .putExtra(EXTRA_CACHED_ENGINE_GROUP_ID, cachedEngineGroupId)
589 .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
590 .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
591 }
592 }
593
594 // Delegate that runs all lifecycle and OS hook logic that is common between
595 // FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
596 // implementation for details about why it exists.
597 @VisibleForTesting protected FlutterActivityAndFragmentDelegate delegate;
598
599 @NonNull private LifecycleRegistry lifecycle;
600
602 lifecycle = new LifecycleRegistry(this);
603 }
604
605 /**
606 * This method exists so that JVM tests can ensure that a delegate exists without putting this
607 * Activity through any lifecycle events, because JVM tests cannot handle executing any lifecycle
608 * methods, at the time of writing this.
609 *
610 * <p>The testing infrastructure should be upgraded to make FlutterActivity tests easy to write
611 * while exercising real lifecycle methods. At such a time, this method should be removed.
612 *
613 * @param delegate The delegate.
614 */
615 // TODO(mattcarroll): remove this when tests allow for it
616 // (https://github.com/flutter/flutter/issues/43798)
617 @VisibleForTesting
619 this.delegate = delegate;
620 }
621
622 /**
623 * Returns the Android App Component exclusively attached to {@link
624 * io.flutter.embedding.engine.FlutterEngine}.
625 */
626 @Override
630
631 @Override
632 protected void onCreate(@Nullable Bundle savedInstanceState) {
633 switchLaunchThemeForNormalTheme();
634
635 super.onCreate(savedInstanceState);
636
638 delegate.onAttach(this);
639 delegate.onRestoreInstanceState(savedInstanceState);
640
641 lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
642
643 configureWindowForTransparency();
644
645 setContentView(createFlutterView());
646
647 configureStatusBarForFullscreenFlutterExperience();
648 }
649
650 /**
651 * Registers the callback with OnBackInvokedDispatcher to capture back navigation gestures and
652 * pass them to the framework.
653 *
654 * <p>This replaces the deprecated onBackPressed method override in order to support API 33's
655 * predictive back navigation feature.
656 *
657 * <p>The callback must be unregistered in order to prevent unpredictable behavior once outside
658 * the Flutter app.
659 */
660 @VisibleForTesting
662 if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
663 getOnBackInvokedDispatcher()
664 .registerOnBackInvokedCallback(
665 OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
666 hasRegisteredBackCallback = true;
667 }
668 }
669
670 /**
671 * Unregisters the callback from OnBackInvokedDispatcher.
672 *
673 * <p>This should be called when the activity is no longer in use to prevent unpredictable
674 * behavior such as being stuck and unable to press back.
675 */
676 @VisibleForTesting
678 if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
679 getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
680 hasRegisteredBackCallback = false;
681 }
682 }
683
684 private final OnBackInvokedCallback onBackInvokedCallback =
685 Build.VERSION.SDK_INT < API_LEVELS.API_33 ? null : createOnBackInvokedCallback();
686
687 @VisibleForTesting
688 protected OnBackInvokedCallback getOnBackInvokedCallback() {
689 return onBackInvokedCallback;
690 }
691
692 @NonNull
693 @TargetApi(API_LEVELS.API_33)
694 @RequiresApi(API_LEVELS.API_33)
695 private OnBackInvokedCallback createOnBackInvokedCallback() {
696 if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) {
697 return new OnBackAnimationCallback() {
698 @Override
699 public void onBackInvoked() {
701 }
702
703 @Override
704 public void onBackCancelled() {
706 }
707
708 @Override
709 public void onBackProgressed(@NonNull BackEvent backEvent) {
710 updateBackGestureProgress(backEvent);
711 }
712
713 @Override
714 public void onBackStarted(@NonNull BackEvent backEvent) {
715 startBackGesture(backEvent);
716 }
717 };
718 }
719
720 return this::onBackPressed;
721 }
722
723 @Override
724 public void setFrameworkHandlesBack(boolean frameworkHandlesBack) {
725 if (frameworkHandlesBack && !hasRegisteredBackCallback) {
727 } else if (!frameworkHandlesBack && hasRegisteredBackCallback) {
729 }
730 }
731
732 /**
733 * Switches themes for this {@code Activity} from the theme used to launch this {@code Activity}
734 * to a "normal theme" that is intended for regular {@code Activity} operation.
735 *
736 * <p>This behavior is offered so that a "launch screen" can be displayed while the application
737 * initially loads. To utilize this behavior in an app, do the following:
738 *
739 * <ol>
740 * <li>Create 2 different themes in style.xml: one theme for the launch screen and one theme for
741 * normal display.
742 * <li>In the launch screen theme, set the "windowBackground" property to a {@code Drawable} of
743 * your choice.
744 * <li>In the normal theme, customize however you'd like.
745 * <li>In the AndroidManifest.xml, set the theme of your {@code FlutterActivity} to your launch
746 * theme.
747 * <li>Add a {@code <meta-data>} property to your {@code FlutterActivity} with a name of
748 * "io.flutter.embedding.android.NormalTheme" and set the resource to your normal theme,
749 * e.g., {@code android:resource="@style/MyNormalTheme}.
750 * </ol>
751 *
752 * With the above settings, your launch theme will be used when loading the app, and then the
753 * theme will be switched to your normal theme once the app has initialized.
754 *
755 * <p>Do not change aspects of system chrome between a launch theme and normal theme. Either
756 * define both themes to be fullscreen or not, and define both themes to display the same status
757 * bar and navigation bar settings. If you wish to adjust system chrome once your Flutter app
758 * renders, use platform channels to instruct Android to do so at the appropriate time. This will
759 * avoid any jarring visual changes during app startup.
760 */
761 private void switchLaunchThemeForNormalTheme() {
762 try {
763 Bundle metaData = getMetaData();
764 if (metaData != null) {
765 int normalThemeRID = metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
766 if (normalThemeRID != -1) {
767 setTheme(normalThemeRID);
768 }
769 } else {
770 Log.v(TAG, "Using the launch theme as normal theme.");
771 }
772 } catch (PackageManager.NameNotFoundException exception) {
773 Log.e(
774 TAG,
775 "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme.");
776 }
777 }
778
779 /**
780 * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
781 * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link
782 * BackgroundMode#transparent}.
783 *
784 * <p>For {@code Activity} transparency to work as expected, the theme applied to this {@code
785 * Activity} must include {@code <item name="android:windowIsTranslucent">true</item>}.
786 */
787 private void configureWindowForTransparency() {
788 BackgroundMode backgroundMode = getBackgroundMode();
789 if (backgroundMode == BackgroundMode.transparent) {
790 getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
791 }
792 }
793
794 @NonNull
795 private View createFlutterView() {
796 return delegate.onCreateView(
797 /* inflater=*/ null,
798 /* container=*/ null,
799 /* savedInstanceState=*/ null,
800 /*flutterViewId=*/ FLUTTER_VIEW_ID,
801 /*shouldDelayFirstAndroidViewDraw=*/ getRenderMode() == RenderMode.surface);
802 }
803
804 private void configureStatusBarForFullscreenFlutterExperience() {
805 Window window = getWindow();
806 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
807 window.setStatusBarColor(0x40000000);
808 window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
809 }
810
811 @Override
812 protected void onStart() {
813 super.onStart();
814 lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
815 if (stillAttachedForEvent("onStart")) {
817 }
818 }
819
820 @Override
821 protected void onResume() {
822 super.onResume();
823 lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
824 if (stillAttachedForEvent("onResume")) {
826 }
827 }
828
829 @Override
830 public void onPostResume() {
831 super.onPostResume();
832 if (stillAttachedForEvent("onPostResume")) {
834 }
835 }
836
837 @Override
838 protected void onPause() {
839 super.onPause();
840 if (stillAttachedForEvent("onPause")) {
842 }
843 lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
844 }
845
846 @Override
847 protected void onStop() {
848 super.onStop();
849 if (stillAttachedForEvent("onStop")) {
851 }
852 lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
853 }
854
855 @Override
856 protected void onSaveInstanceState(Bundle outState) {
857 super.onSaveInstanceState(outState);
858 if (stillAttachedForEvent("onSaveInstanceState")) {
860 }
861 }
862
863 /**
864 * Irreversibly release this activity's control of the {@link
865 * io.flutter.embedding.engine.FlutterEngine} and its subcomponents.
866 *
867 * <p>Calling will disconnect this activity's view from the Flutter renderer, disconnect this
868 * activity from plugins' {@link ActivityControlSurface}, and stop system channel messages from
869 * this activity.
870 *
871 * <p>After calling, this activity should be disposed immediately and not be re-used.
872 */
873 @VisibleForTesting
874 public void release() {
876 if (delegate != null) {
878 delegate = null;
879 }
880 }
881
882 @Override
884 Log.w(
885 TAG,
886 "FlutterActivity "
887 + this
888 + " connection to the engine "
890 + " evicted by another attaching activity");
891 if (delegate != null) {
894 }
895 }
896
897 @Override
898 protected void onDestroy() {
899 super.onDestroy();
900 if (stillAttachedForEvent("onDestroy")) {
903 }
904 release();
905 lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
906 }
907
908 @Override
909 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
910 if (stillAttachedForEvent("onActivityResult")) {
911 delegate.onActivityResult(requestCode, resultCode, data);
912 }
913 }
914
915 @Override
916 protected void onNewIntent(@NonNull Intent intent) {
917 // TODO(mattcarroll): change G3 lint rule that forces us to call super
918 super.onNewIntent(intent);
919 if (stillAttachedForEvent("onNewIntent")) {
920 delegate.onNewIntent(intent);
921 }
922 }
923
924 @Override
925 public void onBackPressed() {
926 if (stillAttachedForEvent("onBackPressed")) {
928 }
929 }
930
931 @TargetApi(API_LEVELS.API_34)
932 @RequiresApi(API_LEVELS.API_34)
933 public void startBackGesture(@NonNull BackEvent backEvent) {
934 if (stillAttachedForEvent("startBackGesture")) {
935 delegate.startBackGesture(backEvent);
936 }
937 }
938
939 @TargetApi(API_LEVELS.API_34)
940 @RequiresApi(API_LEVELS.API_34)
941 public void updateBackGestureProgress(@NonNull BackEvent backEvent) {
942 if (stillAttachedForEvent("updateBackGestureProgress")) {
944 }
945 }
946
947 @TargetApi(API_LEVELS.API_34)
948 @RequiresApi(API_LEVELS.API_34)
949 public void commitBackGesture() {
950 if (stillAttachedForEvent("commitBackGesture")) {
952 }
953 }
954
955 @TargetApi(API_LEVELS.API_34)
956 @RequiresApi(API_LEVELS.API_34)
957 public void cancelBackGesture() {
958 if (stillAttachedForEvent("cancelBackGesture")) {
960 }
961 }
962
963 @Override
965 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
966 if (stillAttachedForEvent("onRequestPermissionsResult")) {
967 delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
968 }
969 }
970
971 @Override
972 public void onUserLeaveHint() {
973 if (stillAttachedForEvent("onUserLeaveHint")) {
975 }
976 }
977
978 @Override
979 public void onWindowFocusChanged(boolean hasFocus) {
980 super.onWindowFocusChanged(hasFocus);
981 if (stillAttachedForEvent("onWindowFocusChanged")) {
983 }
984 }
985
986 @Override
987 public void onTrimMemory(int level) {
988 super.onTrimMemory(level);
989 if (stillAttachedForEvent("onTrimMemory")) {
990 delegate.onTrimMemory(level);
991 }
992 }
993
994 /**
995 * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
996 * FlutterActivityAndFragmentDelegate} to obtain a {@code Context} reference as needed.
997 */
998 @Override
999 @NonNull
1001 return this;
1002 }
1003
1004 /**
1005 * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
1006 * FlutterActivityAndFragmentDelegate} to obtain an {@code Activity} reference as needed. This
1007 * reference is used by the delegate to instantiate a {@link FlutterView}, a {@link
1008 * PlatformPlugin}, and to determine if the {@code Activity} is changing configurations.
1009 */
1010 @Override
1011 @NonNull
1012 public Activity getActivity() {
1013 return this;
1014 }
1015
1016 /**
1017 * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
1018 * FlutterActivityAndFragmentDelegate} to obtain a {@code Lifecycle} reference as needed. This
1019 * reference is used by the delegate to provide Flutter plugins with access to lifecycle events.
1020 */
1021 @Override
1022 @NonNull
1023 public Lifecycle getLifecycle() {
1024 return lifecycle;
1025 }
1026
1027 /**
1028 * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
1029 * FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when initializing
1030 * Flutter.
1031 */
1032 @NonNull
1033 @Override
1034 public FlutterShellArgs getFlutterShellArgs() {
1035 return FlutterShellArgs.fromIntent(getIntent());
1036 }
1037
1038 /**
1039 * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to use
1040 * within this {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does not
1041 * want to use a cached {@link io.flutter.embedding.engine.FlutterEngine}.
1042 */
1043 @Override
1044 @Nullable
1045 public String getCachedEngineId() {
1046 return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
1047 }
1048
1049 /**
1050 * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngineGroup} to
1051 * use within this {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does
1052 * not want to use a cached {@link io.flutter.embedding.engine.FlutterEngineGroup}.
1053 */
1054 @Override
1055 @Nullable
1056 public String getCachedEngineGroupId() {
1057 return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_GROUP_ID);
1058 }
1059
1060 /**
1061 * Returns false if the {@link io.flutter.embedding.engine.FlutterEngine} backing this {@code
1062 * FlutterActivity} should outlive this {@code FlutterActivity}, or true to be destroyed when the
1063 * {@code FlutterActivity} is destroyed.
1064 *
1065 * <p>The default value is {@code true} in cases where {@code FlutterActivity} created its own
1066 * {@link io.flutter.embedding.engine.FlutterEngine}, and {@code false} in cases where a cached
1067 * {@link io.flutter.embedding.engine.FlutterEngine} was provided.
1068 */
1069 @Override
1071 boolean explicitDestructionRequested =
1072 getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
1073 if (getCachedEngineId() != null || delegate.isFlutterEngineFromHost()) {
1074 // Only destroy a cached engine if explicitly requested by app developer.
1075 return explicitDestructionRequested;
1076 } else {
1077 // If this Activity created the FlutterEngine, destroy it by default unless
1078 // explicitly requested not to.
1079 return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
1080 }
1081 }
1082
1083 /**
1084 * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
1085 *
1086 * <p>This preference can be controlled with 2 methods:
1087 *
1088 * <ol>
1089 * <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_DART_ENTRYPOINT} with the
1090 * launching {@code Intent}, or
1091 * <li>Set a {@code <meta-data>} called {@link
1092 * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_META_DATA_KEY} within the Android manifest
1093 * definition for this {@code FlutterActivity}
1094 * </ol>
1095 *
1096 * If both preferences are set, the {@code Intent} preference takes priority.
1097 *
1098 * <p>Subclasses may override this method to directly control the Dart entrypoint.
1099 */
1100 @NonNull
1102 if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) {
1103 return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT);
1104 }
1105
1106 try {
1107 Bundle metaData = getMetaData();
1108 String desiredDartEntrypoint =
1109 metaData != null ? metaData.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
1110 return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
1111 } catch (PackageManager.NameNotFoundException e) {
1112 return DEFAULT_DART_ENTRYPOINT;
1113 }
1114 }
1115
1116 /**
1117 * The Dart entrypoint arguments will be passed as a list of string to Dart's entrypoint function.
1118 *
1119 * <p>A value of null means do not pass any arguments to Dart's entrypoint function.
1120 *
1121 * <p>Subclasses may override this method to directly control the Dart entrypoint arguments.
1122 */
1123 @Nullable
1125 return (List<String>) getIntent().getSerializableExtra(EXTRA_DART_ENTRYPOINT_ARGS);
1126 }
1127
1128 /**
1129 * The Dart library URI for the entrypoint that will be executed as soon as the Dart snapshot is
1130 * loaded.
1131 *
1132 * <p>Example value: "package:foo/bar.dart"
1133 *
1134 * <p>This preference can be controlled by setting a {@code <meta-data>} called {@link
1135 * FlutterActivityLaunchConfigs#DART_ENTRYPOINT_URI_META_DATA_KEY} within the Android manifest
1136 * definition for this {@code FlutterActivity}.
1137 *
1138 * <p>A value of null means use the default root library.
1139 *
1140 * <p>Subclasses may override this method to directly control the Dart entrypoint uri.
1141 */
1142 @Nullable
1144 try {
1145 Bundle metaData = getMetaData();
1146 String desiredDartLibraryUri =
1147 metaData != null ? metaData.getString(DART_ENTRYPOINT_URI_META_DATA_KEY) : null;
1148 return desiredDartLibraryUri;
1149 } catch (PackageManager.NameNotFoundException e) {
1150 return null;
1151 }
1152 }
1153
1154 /**
1155 * The initial route that a Flutter app will render upon loading and executing its Dart code.
1156 *
1157 * <p>This preference can be controlled with 2 methods:
1158 *
1159 * <ol>
1160 * <li>Pass a boolean as {@link FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE} with the
1161 * launching {@code Intent}, or
1162 * <li>Set a {@code <meta-data>} called {@link
1163 * FlutterActivityLaunchConfigs#INITIAL_ROUTE_META_DATA_KEY} for this {@code Activity} in
1164 * the Android manifest.
1165 * </ol>
1166 *
1167 * If both preferences are set, the {@code Intent} preference takes priority.
1168 *
1169 * <p>The reason that a {@code <meta-data>} preference is supported is because this {@code
1170 * Activity} might be the very first {@code Activity} launched, which means the developer won't
1171 * have control over the incoming {@code Intent}.
1172 *
1173 * <p>Subclasses may override this method to directly control the initial route.
1174 *
1175 * <p>If this method returns null and the {@code shouldHandleDeeplinking} returns true, the
1176 * initial route is derived from the {@code Intent} through the Intent.getData() instead.
1177 */
1178 public String getInitialRoute() {
1179 if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
1180 return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
1181 }
1182
1183 try {
1184 Bundle metaData = getMetaData();
1185 String desiredInitialRoute =
1186 metaData != null ? metaData.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
1187 return desiredInitialRoute;
1188 } catch (PackageManager.NameNotFoundException e) {
1189 return null;
1190 }
1191 }
1192
1193 /**
1194 * A custom path to the bundle that contains this Flutter app's resources, e.g., Dart code
1195 * snapshots.
1196 *
1197 * <p>When this {@code FlutterActivity} is run by Flutter tooling and a data String is included in
1198 * the launching {@code Intent}, that data String is interpreted as an app bundle path.
1199 *
1200 * <p>When otherwise unspecified, the value is null, which defaults to the app bundle path defined
1201 * in {@link io.flutter.embedding.engine.loader.FlutterLoader#findAppBundlePath()}.
1202 *
1203 * <p>Subclasses may override this method to return a custom app bundle path.
1204 */
1205 @NonNull
1206 public String getAppBundlePath() {
1207 // If this Activity was launched from tooling, and the incoming Intent contains
1208 // a custom app bundle path, return that path.
1209 // TODO(mattcarroll): determine if we should have an explicit FlutterTestActivity instead of
1210 // conflating.
1211 if (isDebuggable() && Intent.ACTION_RUN.equals(getIntent().getAction())) {
1212 String appBundlePath = getIntent().getDataString();
1213 if (appBundlePath != null) {
1214 return appBundlePath;
1215 }
1216 }
1217
1218 return null;
1219 }
1220
1221 /**
1222 * Returns true if Flutter is running in "debug mode", and false otherwise.
1223 *
1224 * <p>Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
1225 */
1226 private boolean isDebuggable() {
1227 return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
1228 }
1229
1230 /**
1231 * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
1232 * FlutterActivityAndFragmentDelegate} to obtain the desired {@link RenderMode} that should be
1233 * used when instantiating a {@link FlutterView}.
1234 */
1235 @NonNull
1236 @Override
1238 return getBackgroundMode() == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture;
1239 }
1240
1241 /**
1242 * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by {@link
1243 * FlutterActivityAndFragmentDelegate} to obtain the desired {@link TransparencyMode} that should
1244 * be used when instantiating a {@link FlutterView}.
1245 */
1246 @NonNull
1247 @Override
1249 return getBackgroundMode() == BackgroundMode.opaque
1250 ? TransparencyMode.opaque
1252 }
1253
1254 /**
1255 * The desired window background mode of this {@code Activity}, which defaults to {@link
1256 * BackgroundMode#opaque}.
1257 *
1258 * @return The background mode.
1259 */
1260 @NonNull
1261 protected BackgroundMode getBackgroundMode() {
1262 if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
1263 return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
1264 } else {
1265 return BackgroundMode.opaque;
1266 }
1267 }
1268
1269 /**
1270 * Hook for subclasses to easily provide a custom {@link
1271 * io.flutter.embedding.engine.FlutterEngine}.
1272 *
1273 * <p>This hook is where a cached {@link io.flutter.embedding.engine.FlutterEngine} should be
1274 * provided, if a cached {@link FlutterEngine} is desired.
1275 */
1276 @Nullable
1277 @Override
1278 public FlutterEngine provideFlutterEngine(@NonNull Context context) {
1279 // No-op. Hook for subclasses.
1280 return null;
1281 }
1282
1283 /**
1284 * Hook for subclasses to obtain a reference to the {@link
1285 * io.flutter.embedding.engine.FlutterEngine} that is owned by this {@code FlutterActivity}.
1286 *
1287 * @return The Flutter engine.
1288 */
1289 @Nullable
1291 return delegate.getFlutterEngine();
1292 }
1293
1294 /**
1295 * Retrieves the meta data specified in the AndroidManifest.xml.
1296 *
1297 * @return The meta data.
1298 * @throws PackageManager.NameNotFoundException if a package with the given name cannot be found
1299 * on the system.
1300 */
1301 @Nullable
1302 protected Bundle getMetaData() throws PackageManager.NameNotFoundException {
1303 ActivityInfo activityInfo =
1304 getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
1305 return activityInfo.metaData;
1306 }
1307
1308 @Nullable
1309 @Override
1310 public PlatformPlugin providePlatformPlugin(
1311 @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
1312 return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this);
1313 }
1314
1315 /**
1316 * Hook for subclasses to easily configure a {@code FlutterEngine}.
1317 *
1318 * <p>This method is called after {@link #provideFlutterEngine(Context)}.
1319 *
1320 * <p>All plugins listed in the app's pubspec are registered in the base implementation of this
1321 * method unless the FlutterEngine for this activity was externally created. To avoid the
1322 * automatic plugin registration for implicitly created FlutterEngines, override this method
1323 * without invoking super(). To keep automatic plugin registration and further configure the
1324 * FlutterEngine, override this method, invoke super(), and then configure the FlutterEngine as
1325 * desired.
1326 */
1327 @Override
1328 public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
1329 if (delegate.isFlutterEngineFromHost()) {
1330 // If the FlutterEngine was explicitly built and injected into this FlutterActivity, the
1331 // builder should explicitly decide whether to automatically register plugins via the
1332 // FlutterEngine's construction parameter or via the AndroidManifest metadata.
1333 return;
1334 }
1335
1336 GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
1337 }
1338
1339 /**
1340 * Hook for the host to cleanup references that were established in {@link
1341 * #configureFlutterEngine(FlutterEngine)} before the host is destroyed or detached.
1342 *
1343 * <p>This method is called in {@link #onDestroy()}.
1344 */
1345 @Override
1346 public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
1347 // No-op. Hook for subclasses.
1348 }
1349
1350 /**
1351 * Hook for subclasses to control whether or not the {@link FlutterFragment} within this {@code
1352 * Activity} automatically attaches its {@link io.flutter.embedding.engine.FlutterEngine} to this
1353 * {@code Activity}.
1354 *
1355 * <p>This property is controlled with a protected method instead of an {@code Intent} argument
1356 * because the only situation where changing this value would help, is a situation in which {@code
1357 * FlutterActivity} is being subclassed to utilize a custom and/or cached {@link
1358 * io.flutter.embedding.engine.FlutterEngine}.
1359 *
1360 * <p>Defaults to {@code true}.
1361 *
1362 * <p>Control surfaces are used to provide Android resources and lifecycle events to plugins that
1363 * are attached to the {@link io.flutter.embedding.engine.FlutterEngine}. If {@code
1364 * shouldAttachEngineToActivity} is true, then this {@code FlutterActivity} will connect its
1365 * {@link io.flutter.embedding.engine.FlutterEngine} to itself, along with any plugins that are
1366 * registered with that {@link io.flutter.embedding.engine.FlutterEngine}. This allows plugins to
1367 * access the {@code Activity}, as well as receive {@code Activity}-specific calls, e.g. {@link
1368 * Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false, then this
1369 * {@code FlutterActivity} will not automatically manage the connection between its {@link
1370 * FlutterEngine} and itself. In this case, plugins will not be offered a reference to an {@code
1371 * Activity} or its OS hooks.
1372 *
1373 * <p>Returning false from this method does not preclude a {@link
1374 * io.flutter.embedding.engine.FlutterEngine} from being attaching to a {@code FlutterActivity} -
1375 * it just prevents the attachment from happening automatically. A developer can choose to
1376 * subclass {@code FlutterActivity} and then invoke {@link
1377 * ActivityControlSurface#attachToActivity(ExclusiveAppComponent, Lifecycle)} and {@link
1378 * ActivityControlSurface#detachFromActivity()} at the desired times.
1379 *
1380 * <p>One reason that a developer might choose to manually manage the relationship between the
1381 * {@code Activity} and {@link io.flutter.embedding.engine.FlutterEngine} is if the developer
1382 * wants to move the {@link FlutterEngine} somewhere else. For example, a developer might want the
1383 * {@link io.flutter.embedding.engine.FlutterEngine} to outlive this {@code FlutterActivity} so
1384 * that it can be used later in a different {@code Activity}. To accomplish this, the {@link
1385 * io.flutter.embedding.engine.FlutterEngine} may need to be disconnected from this {@code
1386 * FlutterActivity} at an unusual time, preventing this {@code FlutterActivity} from correctly
1387 * managing the relationship between the {@link io.flutter.embedding.engine.FlutterEngine} and
1388 * itself.
1389 */
1390 @Override
1392 return true;
1393 }
1394
1395 /**
1396 * Whether to handle the deeplinking from the {@code Intent} automatically if the {@code
1397 * getInitialRoute} returns null.
1398 *
1399 * <p>The default implementation looks {@code <meta-data>} called {@link
1400 * FlutterActivityLaunchConfigs#HANDLE_DEEPLINKING_META_DATA_KEY} within the Android manifest
1401 * definition for this {@code FlutterActivity}.
1402 */
1403 @Override
1404 public boolean shouldHandleDeeplinking() {
1405 try {
1406 Bundle metaData = getMetaData();
1407 boolean shouldHandleDeeplinking =
1408 metaData != null ? metaData.getBoolean(HANDLE_DEEPLINKING_META_DATA_KEY) : false;
1410 } catch (PackageManager.NameNotFoundException e) {
1411 return false;
1412 }
1413 }
1414
1415 @Override
1416 public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {
1417 // Hook for subclasses.
1418 }
1419
1420 @Override
1421 public void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView) {
1422 // Hook for subclasses.
1423 }
1424
1425 @Override
1426 public void onFlutterUiDisplayed() {
1427 // Notifies Android that we're fully drawn so that performance metrics can be collected by
1428 // Flutter performance tests. A few considerations:
1429 // * reportFullyDrawn was supported in KitKat (API 19), but has a bug around requiring
1430 // permissions in some Android versions.
1431 // * reportFullyDrawn behavior isn't tested on pre-Q versions.
1432 // See https://github.com/flutter/flutter/issues/46172, and
1433 // https://github.com/flutter/flutter/issues/88767.
1434 if (Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
1435 reportFullyDrawn();
1436 }
1437 }
1438
1439 @Override
1441 // no-op
1442 }
1443
1444 @Override
1445 public boolean shouldRestoreAndSaveState() {
1446 if (getIntent().hasExtra(EXTRA_ENABLE_STATE_RESTORATION)) {
1447 return getIntent().getBooleanExtra(EXTRA_ENABLE_STATE_RESTORATION, false);
1448 }
1449 if (getCachedEngineId() != null) {
1450 // Prevent overwriting the existing state in a cached engine with restoration state.
1451 return false;
1452 }
1453 return true;
1454 }
1455
1456 /**
1457 * Give the host application a chance to take control of the app lifecycle events.
1458 *
1459 * <p>Return {@code false} means the host application dispatches these app lifecycle events, while
1460 * return {@code true} means the engine dispatches these events.
1461 *
1462 * <p>Defaults to {@code true}.
1463 */
1464 @Override
1466 return true;
1467 }
1468
1469 /**
1470 * Whether to automatically attach the {@link FlutterView} to the engine.
1471 *
1472 * <p>Returning {@code false} means that the task of attaching the {@link FlutterView} to the
1473 * engine will be taken over by the host application.
1474 *
1475 * <p>Defaults to {@code true}.
1476 */
1477 @Override
1479 return true;
1480 }
1481
1482 @Override
1483 public boolean popSystemNavigator() {
1484 // Hook for subclass. No-op if returns false.
1485 return false;
1486 }
1487
1488 @Override
1490 if (delegate != null) {
1492 }
1493 }
1494
1495 private boolean stillAttachedForEvent(String event) {
1496 if (delegate == null) {
1497 Log.w(TAG, "FlutterActivity " + hashCode() + " " + event + " called after release.");
1498 return false;
1499 }
1500 if (!delegate.isAttached()) {
1501 Log.w(TAG, "FlutterActivity " + hashCode() + " " + event + " called after detach.");
1502 return false;
1503 }
1504 return true;
1505 }
1506}
static void v(@NonNull String tag, @NonNull String message)
Definition Log.java:40
static void w(@NonNull String tag, @NonNull String message)
Definition Log.java:76
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState, int flutterViewId, boolean shouldDelayFirstAndroidViewDraw)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode)
CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity)
CachedEngineIntentBuilder( @NonNull Class<? extends FlutterActivity > activityClass, @NonNull String engineId)
NewEngineInGroupIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode)
NewEngineInGroupIntentBuilder( @NonNull Class<? extends FlutterActivity > activityClass, @NonNull String engineGroupId)
NewEngineInGroupIntentBuilder initialRoute(@NonNull String initialRoute)
NewEngineInGroupIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint)
NewEngineIntentBuilder initialRoute(@NonNull String initialRoute)
NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode)
NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity > activityClass)
NewEngineIntentBuilder dartEntrypointArgs(@Nullable List< String > dartEntrypointArgs)
void setFrameworkHandlesBack(boolean frameworkHandlesBack)
static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView)
void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate)
void updateBackGestureProgress(@NonNull BackEvent backEvent)
void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView)
void startBackGesture(@NonNull BackEvent backEvent)
FlutterEngine provideFlutterEngine(@NonNull Context context)
ExclusiveAppComponent< Activity > getExclusiveAppComponent()
PlatformPlugin providePlatformPlugin( @Nullable Activity activity, @NonNull FlutterEngine flutterEngine)
static Intent createDefaultIntent(@NonNull Context launchContext)
static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId)
static NewEngineIntentBuilder withNewEngine()
void onCreate(@Nullable Bundle savedInstanceState)
void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine)
FlutterActivityAndFragmentDelegate delegate
void onActivityResult(int requestCode, int resultCode, Intent data)
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
GLFWwindow * window
Definition main.cc:45
FlutterSemanticsFlag flags
FlKeyEvent * event
#define TAG()