// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.app; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources.NotFoundException; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager.LayoutParams; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.util.Preconditions; import io.flutter.view.FlutterMain; import io.flutter.view.FlutterNativeView; import io.flutter.view.FlutterRunArguments; import io.flutter.view.FlutterView; import java.util.ArrayList; /** * Class that performs the actual work of tying Android {@link Activity} instances to Flutter. * *
This exists as a dedicated class (as opposed to being integrated directly into {@link * FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}. * The most obvious example of when this may come in handy is if an application wishes to subclass * the Android v4 support library's {@code FragmentActivity}. * *
To wire this class up to your activity, simply forward the events defined in {@link * FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make * your activity implement {@link PluginRegistry} and/or {@link * io.flutter.view.FlutterView.Provider} and forward those methods to this class as well. */ public final class FlutterActivityDelegate implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry { private static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.app.android.SplashScreenUntilFirstFrame"; private static final String TAG = "FlutterActivityDelegate"; private static final LayoutParams matchParent = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); /** * Specifies the mechanism by which Flutter views are created during the operation of a {@code * FlutterActivityDelegate}. * *
A delegate's view factory will be consulted during {@link #onCreate(Bundle)}. If it returns * {@code null}, then the delegate will fall back to instantiating a new full-screen {@code * FlutterView}. * *
A delegate's native view factory will be consulted during {@link #onCreate(Bundle)}. If it
* returns {@code null}, then the delegate will fall back to instantiating a new {@code
* FlutterNativeView}. This is useful for applications to override to reuse the FlutterNativeView
* held e.g. by a pre-existing background service.
*/
public interface ViewFactory {
FlutterView createFlutterView(Context context);
FlutterNativeView createFlutterNativeView();
/**
* Hook for subclasses to indicate that the {@code FlutterNativeView} returned by {@link
* #createFlutterNativeView()} should not be destroyed when this activity is destroyed.
*/
boolean retainFlutterNativeView();
}
private final Activity activity;
private final ViewFactory viewFactory;
private FlutterView flutterView;
private View launchView;
public FlutterActivityDelegate(Activity activity, ViewFactory viewFactory) {
this.activity = Preconditions.checkNotNull(activity);
this.viewFactory = Preconditions.checkNotNull(viewFactory);
}
@Override
public FlutterView getFlutterView() {
return flutterView;
}
// The implementation of PluginRegistry forwards to flutterView.
@Override
public boolean hasPlugin(String key) {
return flutterView.getPluginRegistry().hasPlugin(key);
}
@Override
@SuppressWarnings("unchecked")
public Returns null if no {@code windowBackground} is set for the activity.
*/
private View createLaunchView() {
if (!showSplashScreenUntilFirstFrame()) {
return null;
}
final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
if (launchScreenDrawable == null) {
return null;
}
final View view = new View(activity);
view.setLayoutParams(matchParent);
view.setBackground(launchScreenDrawable);
return view;
}
/**
* Extracts a {@link Drawable} from the parent activity's {@code windowBackground}.
*
* {@code android:windowBackground} is specifically reused instead of a other attributes
* because the Android framework can display it fast enough when launching the app as opposed to
* anything defined in the Activity subclass.
*
* Returns null if no {@code windowBackground} is set for the activity.
*/
@SuppressWarnings("deprecation")
private Drawable getLaunchScreenDrawableFromActivityTheme() {
TypedValue typedValue = new TypedValue();
if (!activity.getTheme().resolveAttribute(android.R.attr.windowBackground, typedValue, true)) {
return null;
}
if (typedValue.resourceId == 0) {
return null;
}
try {
return activity.getResources().getDrawable(typedValue.resourceId);
} catch (NotFoundException e) {
Log.e(TAG, "Referenced launch screen windowBackground resource does not exist");
return null;
}
}
/**
* Let the user specify whether the activity's {@code windowBackground} is a launch screen and
* should be shown until the first frame via a If a launch screen is defined in the user application's AndroidManifest.xml as the
* activity's {@code windowBackground}, display it on top of the {@link FlutterView} and remove
* the activity's {@code windowBackground}.
*
* Fade it out and remove it when the {@link FlutterView} renders its first frame.
*/
private void addLaunchView() {
if (launchView == null) {
return;
}
activity.addContentView(launchView, matchParent);
flutterView.addFirstFrameListener(
new FlutterView.FirstFrameListener() {
@Override
public void onFirstFrame() {
FlutterActivityDelegate.this
.launchView
.animate()
.alpha(0f)
// Use Android's default animation duration.
.setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Views added to an Activity's addContentView is always added to its
// root FrameLayout.
((ViewGroup) FlutterActivityDelegate.this.launchView.getParent())
.removeView(FlutterActivityDelegate.this.launchView);
FlutterActivityDelegate.this.launchView = null;
}
});
FlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this);
}
});
// Resets the activity theme from the one containing the launch screen in the window
// background to a blank one since the launch screen is now in a view in front of the
// FlutterView.
//
// We can make this configurable if users want it.
activity.setTheme(android.R.style.Theme_Black_NoTitleBar);
}
}