• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 package io.flutter.embedding.android;
6 
7 import android.app.Activity;
8 import android.arch.lifecycle.Lifecycle;
9 import android.arch.lifecycle.LifecycleOwner;
10 import android.arch.lifecycle.LifecycleRegistry;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.content.pm.ActivityInfo;
14 import android.content.pm.ApplicationInfo;
15 import android.content.pm.PackageManager;
16 import android.graphics.Color;
17 import android.graphics.drawable.ColorDrawable;
18 import android.graphics.drawable.Drawable;
19 import android.os.Build;
20 import android.os.Bundle;
21 import android.support.annotation.NonNull;
22 import android.support.annotation.Nullable;
23 import android.view.View;
24 import android.view.Window;
25 import android.view.WindowManager;
26 
27 import io.flutter.Log;
28 import io.flutter.embedding.engine.FlutterEngine;
29 import io.flutter.embedding.engine.FlutterShellArgs;
30 import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
31 import io.flutter.plugin.platform.PlatformPlugin;
32 import io.flutter.view.FlutterMain;
33 
34 /**
35  * {@code Activity} which displays a fullscreen Flutter UI.
36  * <p>
37  * {@code FlutterActivity} is the simplest and most direct way to integrate Flutter within an
38  * Android app.
39  * <p>
40  * <strong>Dart entrypoint, initial route, and app bundle path</strong>
41  * <p>
42  * The Dart entrypoint executed within this {@code Activity} is "main()" by default. The entrypoint
43  * may be specified explicitly by passing the name of the entrypoint method as a {@code String} in
44  * {@link #EXTRA_DART_ENTRYPOINT}, e.g., "myEntrypoint".
45  * <p>
46  * The Flutter route that is initially loaded within this {@code Activity} is "/". The initial
47  * route may be specified explicitly by passing the name of the route as a {@code String} in
48  * {@link #EXTRA_INITIAL_ROUTE}, e.g., "my/deep/link".
49  * <p>
50  * The Dart entrypoint and initial route can each be controlled using a {@link NewEngineIntentBuilder}
51  * via the following methods:
52  * <ul>
53  *   <li>{@link NewEngineIntentBuilder#dartEntrypoint}</li>
54  *   <li>{@link NewEngineIntentBuilder#initialRoute}</li>
55  * </ul>
56  * <p>
57  * The app bundle path, Dart entrypoint, and initial route can also be controlled in a subclass of
58  * {@code FlutterActivity} by overriding their respective methods:
59  * <ul>
60  *   <li>{@link #getAppBundlePath()}</li>
61  *   <li>{@link #getDartEntrypointFunctionName()}</li>
62  *   <li>{@link #getInitialRoute()}</li>
63  * </ul>
64  * <p>
65  * {@code FlutterActivity} can be used with a cached {@link FlutterEngine} instead of creating a new
66  * one. Use {@link #withCachedEngine(String)} to build a {@code FlutterActivity} {@code Intent} that
67  * is configured to use an existing, cached {@link FlutterEngine}. {@link FlutterEngineCache} is the
68  * cache that is used to obtain a given cached {@link FlutterEngine}. An
69  * {@code IllegalStateException} will be thrown if a cached engine is requested but does not exist
70  * in the cache.
71  * <p>
72  * It is generally recommended to use a cached {@link FlutterEngine} to avoid a momentary delay
73  * when initializing a new {@link FlutterEngine}. The two exceptions to using a cached
74  * {@link FlutterEngine} are:
75  * <p>
76  * <ul>
77  *   <li>When {@code FlutterActivity} is the first {@code Activity} displayed by the app, because
78  *   pre-warming a {@link FlutterEngine} would have no impact in this situation.</li>
79  *   <li>When you are unsure when/if you will need to display a Flutter experience.</li>
80  * </ul>
81  * <p>
82  * The following illustrates how to pre-warm and cache a {@link FlutterEngine}:
83  * <p>
84  * {@code
85  *   // Create and pre-warm a FlutterEngine.
86  *   FlutterEngine flutterEngine = new FlutterEngine(context);
87  *   flutterEngine
88  *     .getDartExecutor()
89  *     .executeDartEntrypoint(DartEntrypoint.createDefault());
90  *
91  *   // Cache the pre-warmed FlutterEngine in the FlutterEngineCache.
92  *   FlutterEngineCache.getInstance().put("my_engine", flutterEngine);
93  * }
94  * <p>
95  * If Flutter is needed in a location that cannot use an {@code Activity}, consider using
96  * a {@link FlutterFragment}. Using a {@link FlutterFragment} requires forwarding some calls from
97  * an {@code Activity} to the {@link FlutterFragment}.
98  * <p>
99  * If Flutter is needed in a location that can only use a {@code View}, consider using a
100  * {@link FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an
101  * {@code Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a
102  * {@code Fragment}.
103  * <p>
104  * <strong>FlutterActivity responsibilities</strong>
105  * <p>
106  * {@code FlutterActivity} maintains the following responsibilities:
107  * <ul>
108  *   <li>Displays an Android launch screen.</li>
109  *   <li>Displays a Flutter splash screen.</li>
110  *   <li>Configures the status bar appearance.</li>
111  *   <li>Chooses the Dart execution app bundle path and entrypoint.</li>
112  *   <li>Chooses Flutter's initial route.</li>
113  *   <li>Renders {@code Activity} transparently, if desired.</li>
114  *   <li>Offers hooks for subclasses to provide and configure a {@link FlutterEngine}.</li>
115  * </ul>
116  * <p>
117  * <strong>Launch Screen and Splash Screen</strong>
118  * <p>
119  * {@code FlutterActivity} supports the display of an Android "launch screen" as well as a
120  * Flutter-specific "splash screen". The launch screen is displayed while the Android application
121  * loads. It is only applicable if {@code FlutterActivity} is the first {@code Activity} displayed
122  * upon loading the app. After the launch screen passes, a splash screen is optionally displayed.
123  * The splash screen is displayed for as long as it takes Flutter to initialize and render its
124  * first frame.
125  * <p>
126  * Use Android themes to display a launch screen. Create two themes: a launch theme and a normal
127  * theme. In the launch theme, set {@code windowBackground} to the desired {@code Drawable} for
128  * the launch screen. In the normal theme, set {@code windowBackground} to any desired background
129  * color that should normally appear behind your Flutter content. In most cases this background
130  * color will never be seen, but for possible transition edge cases it is a good idea to explicitly
131  * replace the launch screen window background with a neutral color.
132  * <p>
133  * Do not change aspects of system chrome between a launch theme and normal theme. Either define
134  * both themes to be fullscreen or not, and define both themes to display the same status bar and
135  * navigation bar settings. To adjust system chrome once the Flutter app renders, use platform
136  * channels to instruct Android to do so at the appropriate time. This will avoid any jarring visual
137  * changes during app startup.
138  * <p>
139  * In the AndroidManifest.xml, set the theme of {@code FlutterActivity} to the defined launch theme.
140  * In the metadata section for {@code FlutterActivity}, defined the following reference to your
141  * normal theme:
142  *
143  * {@code
144  *   <meta-data
145  *     android:name="io.flutter.embedding.android.NormalTheme"
146  *     android:resource="@style/YourNormalTheme"
147  *     />
148  * }
149  *
150  * With themes defined, and AndroidManifest.xml updated, Flutter displays the specified launch
151  * screen until the Android application is initialized.
152  * <p>
153  * Flutter also requires initialization time. To specify a splash screen for Flutter initialization,
154  * subclass {@code FlutterActivity} and override {@link #provideSplashScreen()}. See
155  * {@link SplashScreen} for details on implementing a splash screen.
156  * <p>
157  * Flutter ships with a splash screen that automatically displays the exact same
158  * {@code windowBackground} as the launch theme discussed previously. To use that splash screen,
159  * include the following metadata in AndroidManifest.xml for this {@code FlutterActivity}:
160  *
161  * {@code
162  *   <meta-data
163  *     android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
164  *     android:value="true"
165  *     />
166  * }
167  */
168 public class FlutterActivity extends Activity
169     implements FlutterActivityAndFragmentDelegate.Host,
170     LifecycleOwner {
171   private static final String TAG = "FlutterActivity";
172 
173   // Meta-data arguments, processed from manifest XML.
174   protected static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint";
175   protected static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute";
176   protected static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable";
177   protected static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme";
178 
179   // Intent extra arguments.
180   protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
181   protected static final String EXTRA_INITIAL_ROUTE = "initial_route";
182   protected static final String EXTRA_BACKGROUND_MODE = "background_mode";
183   protected static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id";
184   protected static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity";
185 
186   // Default configuration.
187   protected static final String DEFAULT_DART_ENTRYPOINT = "main";
188   protected static final String DEFAULT_INITIAL_ROUTE = "/";
189   protected static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();
190 
191   /**
192    * Creates an {@link Intent} that launches a {@code FlutterActivity}, which executes
193    * a {@code main()} Dart entrypoint, and displays the "/" route as Flutter's initial route.
194    */
195   @NonNull
createDefaultIntent(@onNull Context launchContext)196   public static Intent createDefaultIntent(@NonNull Context launchContext) {
197     return withNewEngine().build(launchContext);
198   }
199 
200   /**
201    * Creates an {@link NewEngineIntentBuilder}, which can be used to configure an {@link Intent} to
202    * launch a {@code FlutterActivity} that internally creates a new {@link FlutterEngine} using
203    * the desired Dart entrypoint, initial route, etc.
204    */
205   @NonNull
withNewEngine()206   public static NewEngineIntentBuilder withNewEngine() {
207     return new NewEngineIntentBuilder(FlutterActivity.class);
208   }
209 
210   /**
211    * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with a new
212    * {@link FlutterEngine} and the desired configuration.
213    */
214   public static class NewEngineIntentBuilder {
215     private final Class<? extends FlutterActivity> activityClass;
216     private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
217     private String initialRoute = DEFAULT_INITIAL_ROUTE;
218     private String backgroundMode = DEFAULT_BACKGROUND_MODE;
219 
220     /**
221      * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
222      * {@code FlutterActivity}.
223      * <p>
224      * Subclasses of {@code FlutterActivity} should provide their own static version of
225      * {@link #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder}
226      * constructed with a {@code Class} reference to the {@code FlutterActivity} subclass,
227      * e.g.:
228      * <p>
229      * {@code
230      * return new NewEngineIntentBuilder(MyFlutterActivity.class);
231      * }
232      */
NewEngineIntentBuilder(@onNull Class<? extends FlutterActivity> activityClass)233     protected NewEngineIntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
234       this.activityClass = activityClass;
235     }
236 
237     /**
238      * The name of the initial Dart method to invoke, defaults to "main".
239      */
240     @NonNull
dartEntrypoint(@onNull String dartEntrypoint)241     public NewEngineIntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) {
242       this.dartEntrypoint = dartEntrypoint;
243       return this;
244     }
245 
246     /**
247      * The initial route that a Flutter app will render in this {@link FlutterFragment},
248      * defaults to "/".
249      */
250     @NonNull
initialRoute(@onNull String initialRoute)251     public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
252       this.initialRoute = initialRoute;
253       return this;
254     }
255 
256     /**
257      * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
258      * {@link BackgroundMode#transparent}.
259      * <p>
260      * The default background mode is {@link BackgroundMode#opaque}.
261      * <p>
262      * Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
263      * {@link FlutterView} of this {@code FlutterActivity} to be configured with a
264      * {@link FlutterTextureView} to support transparency. This choice has a non-trivial performance
265      * impact. A transparent background should only be used if it is necessary for the app design
266      * being implemented.
267      * <p>
268      * A {@code FlutterActivity} that is configured with a background mode of
269      * {@link BackgroundMode#transparent} must have a theme applied to it that includes the
270      * following property: {@code <item name="android:windowIsTranslucent">true</item>}.
271      */
272     @NonNull
backgroundMode(@onNull BackgroundMode backgroundMode)273     public NewEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
274       this.backgroundMode = backgroundMode.name();
275       return this;
276     }
277 
278     /**
279      * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with
280      * the desired configuration.
281      */
282     @NonNull
build(@onNull Context context)283     public Intent build(@NonNull Context context) {
284       return new Intent(context, activityClass)
285           .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
286           .putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
287           .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode)
288           .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, true);
289     }
290   }
291 
292   /**
293    * Creates a {@link CachedEngineIntentBuilder}, which can be used to configure an {@link Intent}
294    * to launch a {@code FlutterActivity} that internally uses an existing {@link FlutterEngine} that
295    * is cached in {@link FlutterEngineCache}.
296    */
withCachedEngine(@onNull String cachedEngineId)297   public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
298     return new CachedEngineIntentBuilder(FlutterActivity.class, cachedEngineId);
299   }
300 
301   /**
302    * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with an existing
303    * {@link FlutterEngine} that is cached in {@link FlutterEngineCache}.
304    */
305   public static class CachedEngineIntentBuilder {
306     private final Class<? extends FlutterActivity> activityClass;
307     private final String cachedEngineId;
308     private boolean destroyEngineWithActivity = false;
309     private String backgroundMode = DEFAULT_BACKGROUND_MODE;
310 
311     /**
312      * Constructor that allows this {@code CachedEngineIntentBuilder} to be used by subclasses of
313      * {@code FlutterActivity}.
314      * <p>
315      * Subclasses of {@code FlutterActivity} should provide their own static version of
316      * {@link #withNewEngine()}, which returns an instance of {@code CachedEngineIntentBuilder}
317      * constructed with a {@code Class} reference to the {@code FlutterActivity} subclass,
318      * e.g.:
319      * <p>
320      * {@code
321      * return new CachedEngineIntentBuilder(MyFlutterActivity.class, engineId);
322      * }
323      */
CachedEngineIntentBuilder( @onNull Class<? extends FlutterActivity> activityClass, @NonNull String engineId )324     protected CachedEngineIntentBuilder(
325         @NonNull Class<? extends FlutterActivity> activityClass,
326         @NonNull String engineId
327     ) {
328       this.activityClass = activityClass;
329       this.cachedEngineId = engineId;
330     }
331 
332     /**
333      * Returns true if the cached {@link FlutterEngine} should be destroyed and removed from the
334      * cache when this {@code FlutterActivity} is destroyed.
335      * <p>
336      * The default value is {@code false}.
337      */
destroyEngineWithActivity(boolean destroyEngineWithActivity)338     public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
339       this.destroyEngineWithActivity = destroyEngineWithActivity;
340       return this;
341     }
342 
343     /**
344      * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
345      * {@link BackgroundMode#transparent}.
346      * <p>
347      * The default background mode is {@link BackgroundMode#opaque}.
348      * <p>
349      * Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
350      * {@link FlutterView} of this {@code FlutterActivity} to be configured with a
351      * {@link FlutterTextureView} to support transparency. This choice has a non-trivial performance
352      * impact. A transparent background should only be used if it is necessary for the app design
353      * being implemented.
354      * <p>
355      * A {@code FlutterActivity} that is configured with a background mode of
356      * {@link BackgroundMode#transparent} must have a theme applied to it that includes the
357      * following property: {@code <item name="android:windowIsTranslucent">true</item>}.
358      */
359     @NonNull
backgroundMode(@onNull BackgroundMode backgroundMode)360     public CachedEngineIntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
361       this.backgroundMode = backgroundMode.name();
362       return this;
363     }
364 
365     /**
366      * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with
367      * the desired configuration.
368      */
369     @NonNull
build(@onNull Context context)370     public Intent build(@NonNull Context context) {
371       return new Intent(context, activityClass)
372           .putExtra(EXTRA_CACHED_ENGINE_ID, cachedEngineId)
373           .putExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, destroyEngineWithActivity)
374           .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
375     }
376   }
377 
378   // Delegate that runs all lifecycle and OS hook logic that is common between
379   // FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
380   // implementation for details about why it exists.
381   private FlutterActivityAndFragmentDelegate delegate;
382 
383   @NonNull
384   private LifecycleRegistry lifecycle;
385 
FlutterActivity()386   public FlutterActivity() {
387     lifecycle = new LifecycleRegistry(this);
388   }
389 
390   @Override
onCreate(@ullable Bundle savedInstanceState)391   protected void onCreate(@Nullable Bundle savedInstanceState) {
392     switchLaunchThemeForNormalTheme();
393 
394     super.onCreate(savedInstanceState);
395 
396     lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
397 
398     delegate = new FlutterActivityAndFragmentDelegate(this);
399     delegate.onAttach(this);
400 
401     configureWindowForTransparency();
402     setContentView(createFlutterView());
403     configureStatusBarForFullscreenFlutterExperience();
404   }
405 
406   /**
407    * Switches themes for this {@code Activity} from the theme used to launch this
408    * {@code Activity} to a "normal theme" that is intended for regular {@code Activity}
409    * operation.
410    * <p>
411    * This behavior is offered so that a "launch screen" can be displayed while the
412    * application initially loads. To utilize this behavior in an app, do the following:
413    * <ol>
414    *   <li>Create 2 different themes in style.xml: one theme for the launch screen and
415    *   one theme for normal display.
416    *   <li>In the launch screen theme, set the "windowBackground" property to a {@code Drawable}
417    *   of your choice.
418    *   <li>In the normal theme, customize however you'd like.
419    *   <li>In the AndroidManifest.xml, set the theme of your {@code FlutterActivity} to
420    *   your launch theme.
421    *   <li>Add a {@code <meta-data>} property to your {@code FlutterActivity} with a name
422    *   of "io.flutter.embedding.android.NormalTheme" and set the resource to your normal
423    *   theme, e.g., {@code android:resource="@style/MyNormalTheme}.
424    * </ol>
425    * With the above settings, your launch theme will be used when loading the app, and
426    * then the theme will be switched to your normal theme once the app has initialized.
427    * <p>
428    * Do not change aspects of system chrome between a launch theme and normal theme. Either define
429    * both themes to be fullscreen or not, and define both themes to display the same status bar and
430    * navigation bar settings. If you wish to adjust system chrome once your Flutter app renders, use
431    * platform channels to instruct Android to do so at the appropriate time. This will avoid any
432    * jarring visual changes during app startup.
433    */
switchLaunchThemeForNormalTheme()434   private void switchLaunchThemeForNormalTheme() {
435     try {
436       ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
437       if (activityInfo.metaData != null) {
438         int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
439         if (normalThemeRID != -1) {
440           setTheme(normalThemeRID);
441         }
442       } else {
443         Log.d(TAG, "Using the launch theme as normal theme.");
444       }
445     } catch (PackageManager.NameNotFoundException exception) {
446       Log.e(TAG, "Could not read meta-data for FlutterActivity. Using the launch theme as normal theme.");
447     }
448   }
449 
450   @Nullable
451   @Override
provideSplashScreen()452   public SplashScreen provideSplashScreen() {
453     Drawable manifestSplashDrawable = getSplashScreenFromManifest();
454     if (manifestSplashDrawable != null) {
455       return new DrawableSplashScreen(manifestSplashDrawable);
456     } else {
457       return null;
458     }
459   }
460 
461   /**
462    * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the
463    * {@code AndroidManifest.xml} file, or null if no such splash screen is requested.
464    * <p>
465    * See {@link #SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to be used in a
466    * manifest file.
467    */
468   @Nullable
469   @SuppressWarnings("deprecation")
getSplashScreenFromManifest()470   private Drawable getSplashScreenFromManifest() {
471     try {
472       ActivityInfo activityInfo = getPackageManager().getActivityInfo(
473           getComponentName(),
474           PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
475       );
476       Bundle metadata = activityInfo.metaData;
477       Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null;
478       return splashScreenId != null
479           ? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
480             ? getResources().getDrawable(splashScreenId, getTheme())
481             : getResources().getDrawable(splashScreenId)
482           : null;
483     } catch (PackageManager.NameNotFoundException e) {
484       // This is never expected to happen.
485       return null;
486     }
487   }
488 
489   /**
490    * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
491    * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}.
492    * <p>
493    * For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity}
494    * must include {@code <item name="android:windowIsTranslucent">true</item>}.
495    */
configureWindowForTransparency()496   private void configureWindowForTransparency() {
497     BackgroundMode backgroundMode = getBackgroundMode();
498     if (backgroundMode == BackgroundMode.transparent) {
499       getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
500       getWindow().setFlags(
501         WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
502         WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
503       );
504     }
505   }
506 
507   @NonNull
createFlutterView()508   private View createFlutterView() {
509     return delegate.onCreateView(
510         null /* inflater */,
511         null /* container */,
512         null /* savedInstanceState */);
513   }
514 
configureStatusBarForFullscreenFlutterExperience()515   private void configureStatusBarForFullscreenFlutterExperience() {
516     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
517       Window window = getWindow();
518       window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
519       window.setStatusBarColor(0x40000000);
520       window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
521     }
522   }
523 
524   @Override
onStart()525   protected void onStart() {
526     super.onStart();
527     lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
528     delegate.onStart();
529   }
530 
531   @Override
onResume()532   protected void onResume() {
533     super.onResume();
534     lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
535     delegate.onResume();
536   }
537 
538   @Override
onPostResume()539   public void onPostResume() {
540     super.onPostResume();
541     delegate.onPostResume();
542   }
543 
544   @Override
onPause()545   protected void onPause() {
546     super.onPause();
547     delegate.onPause();
548     lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
549   }
550 
551   @Override
onStop()552   protected void onStop() {
553     super.onStop();
554     delegate.onStop();
555     lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
556   }
557 
558   @Override
onDestroy()559   protected void onDestroy() {
560     super.onDestroy();
561     delegate.onDestroyView();
562     delegate.onDetach();
563     lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
564   }
565 
566   @Override
onActivityResult(int requestCode, int resultCode, Intent data)567   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
568     delegate.onActivityResult(requestCode, resultCode, data);
569   }
570 
571   @Override
onNewIntent(@onNull Intent intent)572   protected void onNewIntent(@NonNull Intent intent) {
573     // TODO(mattcarroll): change G3 lint rule that forces us to call super
574     super.onNewIntent(intent);
575     delegate.onNewIntent(intent);
576   }
577 
578   @Override
onBackPressed()579   public void onBackPressed() {
580     delegate.onBackPressed();
581   }
582 
583   @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)584   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
585     delegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
586   }
587 
588   @Override
onUserLeaveHint()589   public void onUserLeaveHint() {
590     delegate.onUserLeaveHint();
591   }
592 
593   @Override
onTrimMemory(int level)594   public void onTrimMemory(int level) {
595     super.onTrimMemory(level);
596     delegate.onTrimMemory(level);
597   }
598 
599   /**
600    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
601    * {@link FlutterActivityAndFragmentDelegate} to obtain a {@code Context} reference as
602    * needed.
603    */
604   @Override
605   @NonNull
getContext()606   public Context getContext() {
607     return this;
608   }
609 
610   /**
611    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
612    * {@link FlutterActivityAndFragmentDelegate} to obtain an {@code Activity} reference as
613    * needed. This reference is used by the delegate to instantiate a {@link FlutterView},
614    * a {@link PlatformPlugin}, and to determine if the {@code Activity} is changing
615    * configurations.
616    */
617   @Override
618   @NonNull
getActivity()619   public Activity getActivity() {
620     return this;
621   }
622 
623   /**
624    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
625    * {@link FlutterActivityAndFragmentDelegate} to obtain a {@code Lifecycle} reference as
626    * needed. This reference is used by the delegate to provide Flutter plugins with access
627    * to lifecycle events.
628    */
629   @Override
630   @NonNull
getLifecycle()631   public Lifecycle getLifecycle() {
632     return lifecycle;
633   }
634 
635   /**
636    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
637    * {@link FlutterActivityAndFragmentDelegate} to obtain Flutter shell arguments when
638    * initializing Flutter.
639    */
640   @NonNull
641   @Override
getFlutterShellArgs()642   public FlutterShellArgs getFlutterShellArgs() {
643     return FlutterShellArgs.fromIntent(getIntent());
644   }
645 
646   /**
647    * Returns the ID of a statically cached {@link FlutterEngine} to use within this
648    * {@code FlutterActivity}, or {@code null} if this {@code FlutterActivity} does not want to
649    * use a cached {@link FlutterEngine}.
650    */
651   @Override
652   @Nullable
getCachedEngineId()653   public String getCachedEngineId() {
654     return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
655   }
656 
657   /**
658    * Returns false if the {@link FlutterEngine} backing this {@code FlutterActivity} should
659    * outlive this {@code FlutterActivity}, or true to be destroyed when the {@code FlutterActivity}
660    * is destroyed.
661    * <p>
662    * The default value is {@code true} in cases where {@code FlutterActivity} created its own
663    * {@link FlutterEngine}, and {@code false} in cases where a cached {@link FlutterEngine} was
664    * provided.
665    */
666   @Override
shouldDestroyEngineWithHost()667   public boolean shouldDestroyEngineWithHost() {
668     return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
669   }
670 
671   /**
672    * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
673    * <p>
674    * This preference can be controlled with 2 methods:
675    * <ol>
676    *   <li>Pass a {@code String} as {@link #EXTRA_DART_ENTRYPOINT} with the launching {@code Intent}, or</li>
677    *   <li>Set a {@code <meta-data>} called {@link #DART_ENTRYPOINT_META_DATA_KEY} for this
678    *       {@code Activity} in the Android manifest.</li>
679    * </ol>
680    * If both preferences are set, the {@code Intent} preference takes priority.
681    * <p>
682    * The reason that a {@code <meta-data>} preference is supported is because this {@code Activity}
683    * might be the very first {@code Activity} launched, which means the developer won't have
684    * control over the incoming {@code Intent}.
685    * <p>
686    * Subclasses may override this method to directly control the Dart entrypoint.
687    */
688   @NonNull
getDartEntrypointFunctionName()689   public String getDartEntrypointFunctionName() {
690     if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) {
691       return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT);
692     }
693 
694     try {
695       ActivityInfo activityInfo = getPackageManager().getActivityInfo(
696           getComponentName(),
697           PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
698       );
699       Bundle metadata = activityInfo.metaData;
700       String desiredDartEntrypoint = metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
701       return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
702     } catch (PackageManager.NameNotFoundException e) {
703       return DEFAULT_DART_ENTRYPOINT;
704     }
705   }
706 
707   /**
708    * The initial route that a Flutter app will render upon loading and executing its Dart code.
709    * <p>
710    * This preference can be controlled with 2 methods:
711    * <ol>
712    *   <li>Pass a boolean as {@link #EXTRA_INITIAL_ROUTE} with the launching {@code Intent}, or</li>
713    *   <li>Set a {@code <meta-data>} called {@link #INITIAL_ROUTE_META_DATA_KEY} for this
714    *    {@code Activity} in the Android manifest.</li>
715    * </ol>
716    * If both preferences are set, the {@code Intent} preference takes priority.
717    * <p>
718    * The reason that a {@code <meta-data>} preference is supported is because this {@code Activity}
719    * might be the very first {@code Activity} launched, which means the developer won't have
720    * control over the incoming {@code Intent}.
721    * <p>
722    * Subclasses may override this method to directly control the initial route.
723    */
724   @NonNull
getInitialRoute()725   public String getInitialRoute() {
726     if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
727       return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
728     }
729 
730     try {
731       ActivityInfo activityInfo = getPackageManager().getActivityInfo(
732           getComponentName(),
733           PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
734       );
735       Bundle metadata = activityInfo.metaData;
736       String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
737       return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
738     } catch (PackageManager.NameNotFoundException e) {
739       return DEFAULT_INITIAL_ROUTE;
740     }
741   }
742 
743   /**
744    * The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots.
745    * <p>
746    * When this {@code FlutterActivity} is run by Flutter tooling and a data String is included
747    * in the launching {@code Intent}, that data String is interpreted as an app bundle path.
748    * <p>
749    * By default, the app bundle path is obtained from {@link FlutterMain#findAppBundlePath()}.
750    * <p>
751    * Subclasses may override this method to return a custom app bundle path.
752    */
753   @NonNull
getAppBundlePath()754   public String getAppBundlePath() {
755     // If this Activity was launched from tooling, and the incoming Intent contains
756     // a custom app bundle path, return that path.
757     // TODO(mattcarroll): determine if we should have an explicit FlutterTestActivity instead of conflating.
758     if (isDebuggable() && Intent.ACTION_RUN.equals(getIntent().getAction())) {
759       String appBundlePath = getIntent().getDataString();
760       if (appBundlePath != null) {
761         return appBundlePath;
762       }
763     }
764 
765     // Return the default app bundle path.
766     // TODO(mattcarroll): move app bundle resolution into an appropriately named class.
767     return FlutterMain.findAppBundlePath();
768   }
769 
770   /**
771    * Returns true if Flutter is running in "debug mode", and false otherwise.
772    * <p>
773    * Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
774    */
isDebuggable()775   private boolean isDebuggable() {
776     return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
777   }
778 
779   /**
780    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
781    * {@link FlutterActivityAndFragmentDelegate} to obtain the desired {@link FlutterView.RenderMode}
782    * that should be used when instantiating a {@link FlutterView}.
783    */
784   @NonNull
785   @Override
getRenderMode()786   public FlutterView.RenderMode getRenderMode() {
787     return getBackgroundMode() == BackgroundMode.opaque
788         ? FlutterView.RenderMode.surface
789         : FlutterView.RenderMode.texture;
790   }
791 
792   /**
793    * {@link FlutterActivityAndFragmentDelegate.Host} method that is used by
794    * {@link FlutterActivityAndFragmentDelegate} to obtain the desired
795    * {@link FlutterView.TransparencyMode} that should be used when instantiating a
796    * {@link FlutterView}.
797    */
798   @NonNull
799   @Override
getTransparencyMode()800   public FlutterView.TransparencyMode getTransparencyMode() {
801     return getBackgroundMode() == BackgroundMode.opaque
802         ? FlutterView.TransparencyMode.opaque
803         : FlutterView.TransparencyMode.transparent;
804   }
805 
806   /**
807    * The desired window background mode of this {@code Activity}, which defaults to
808    * {@link BackgroundMode#opaque}.
809    */
810   @NonNull
getBackgroundMode()811   protected BackgroundMode getBackgroundMode() {
812     if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
813       return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
814     } else {
815       return BackgroundMode.opaque;
816     }
817   }
818 
819   /**
820    * Hook for subclasses to easily provide a custom {@link FlutterEngine}.
821    * <p>
822    * This hook is where a cached {@link FlutterEngine} should be provided, if a cached
823    * {@link FlutterEngine} is desired.
824    */
825   @Nullable
826   @Override
provideFlutterEngine(@onNull Context context)827   public FlutterEngine provideFlutterEngine(@NonNull Context context) {
828     // No-op. Hook for subclasses.
829     return null;
830   }
831 
832   /**
833    * Hook for subclasses to obtain a reference to the {@link FlutterEngine} that is owned
834    * by this {@code FlutterActivity}.
835    */
836   @Nullable
getFlutterEngine()837   protected FlutterEngine getFlutterEngine() {
838     return delegate.getFlutterEngine();
839   }
840 
841   @Nullable
842   @Override
providePlatformPlugin(@ullable Activity activity, @NonNull FlutterEngine flutterEngine)843   public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
844     if (activity != null) {
845       return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
846     } else {
847       return null;
848     }
849   }
850 
851   /**
852    * Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register
853    * plugins.
854    * <p>
855    * This method is called after {@link #provideFlutterEngine(Context)}.
856    */
857   @Override
configureFlutterEngine(@onNull FlutterEngine flutterEngine)858   public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
859     // No-op. Hook for subclasses.
860   }
861 
862   /**
863    * Hook for subclasses to control whether or not the {@link FlutterFragment} within this
864    * {@code Activity} automatically attaches its {@link FlutterEngine} to this {@code Activity}.
865    * <p>
866    * This property is controlled with a protected method instead of an {@code Intent} argument because
867    * the only situation where changing this value would help, is a situation in which
868    * {@code FlutterActivity} is being subclassed to utilize a custom and/or cached {@link FlutterEngine}.
869    * <p>
870    * Defaults to {@code true}.
871    * <p>
872    * Control surfaces are used to provide Android resources and lifecycle events to
873    * plugins that are attached to the {@link FlutterEngine}. If {@code shouldAttachEngineToActivity}
874    * is true then this {@code FlutterActivity} will connect its {@link FlutterEngine} to itself,
875    * along with any plugins that are registered with that {@link FlutterEngine}. This allows
876    * plugins to access the {@code Activity}, as well as receive {@code Activity}-specific calls,
877    * e.g., {@link Activity#onNewIntent(Intent)}. If {@code shouldAttachEngineToActivity} is false,
878    * then this {@code FlutterActivity} will not automatically manage the connection between its
879    * {@link FlutterEngine} and itself. In this case, plugins will not be offered a reference to
880    * an {@code Activity} or its OS hooks.
881    * <p>
882    * Returning false from this method does not preclude a {@link FlutterEngine} from being
883    * attaching to a {@code FlutterActivity} - it just prevents the attachment from happening
884    * automatically. A developer can choose to subclass {@code FlutterActivity} and then
885    * invoke {@link ActivityControlSurface#attachToActivity(Activity, Lifecycle)}
886    * and {@link ActivityControlSurface#detachFromActivity()} at the desired times.
887    * <p>
888    * One reason that a developer might choose to manually manage the relationship between the
889    * {@code Activity} and {@link FlutterEngine} is if the developer wants to move the
890    * {@link FlutterEngine} somewhere else. For example, a developer might want the
891    * {@link FlutterEngine} to outlive this {@code FlutterActivity} so that it can be used
892    * later in a different {@code Activity}. To accomplish this, the {@link FlutterEngine} may
893    * need to be disconnected from this {@code FluttterActivity} at an unusual time, preventing
894    * this {@code FlutterActivity} from correctly managing the relationship between the
895    * {@link FlutterEngine} and itself.
896    */
897   @Override
shouldAttachEngineToActivity()898   public boolean shouldAttachEngineToActivity() {
899     return true;
900   }
901 
902   @Override
onFirstFrameRendered()903   public void onFirstFrameRendered() {}
904 
905   /**
906    * The mode of the background of a {@code FlutterActivity}, either opaque or transparent.
907    */
908   public enum BackgroundMode {
909     /** Indicates a FlutterActivity with an opaque background. This is the default. */
910     opaque,
911     /** Indicates a FlutterActivity with a transparent background. */
912     transparent
913   }
914 }
915