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