[flutter roll] Revert "Determine lifecycle by looking at window focus also" (flutter/engine#41626)
Reverts flutter/engine#41094. context: updated the java/android timeouts and details in b/280204906
This commit is contained in:
@@ -1642,61 +1642,31 @@ class FrameTiming {
|
||||
/// States that an application can be in.
|
||||
///
|
||||
/// The values below describe notifications from the operating system.
|
||||
/// Applications should not expect to always receive all possible notifications.
|
||||
/// For example, if the users pulls out the battery from the device, no
|
||||
/// notification will be sent before the application is suddenly terminated,
|
||||
/// along with the rest of the operating system.
|
||||
///
|
||||
/// For historical and name collision reasons, Flutter's application state names
|
||||
/// do not correspond one to one with the state names on all platforms. On
|
||||
/// Android, for instance, when the OS calls
|
||||
/// [`Activity.onPause`](https://developer.android.com/reference/android/app/Activity#onPause()),
|
||||
/// Flutter will enter the [inactive] state, but when Android calls
|
||||
/// [`Activity.onStop`](https://developer.android.com/reference/android/app/Activity#onStop()),
|
||||
/// Flutter enters the [paused] state. See the individual state's documentation
|
||||
/// for descriptions of what they mean on each platform.
|
||||
/// Applications should not expect to always receive all possible
|
||||
/// notifications. For example, if the users pulls out the battery from the
|
||||
/// device, no notification will be sent before the application is suddenly
|
||||
/// terminated, along with the rest of the operating system.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state
|
||||
/// from the widgets layer.
|
||||
/// * iOS's [IOKit activity lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle?language=objc) documentation.
|
||||
/// * Android's [activity lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle) documentation.
|
||||
/// * macOS's [AppKit activity lifecycle](https://developer.apple.com/documentation/appkit/nsapplicationdelegate?language=objc) documentation.
|
||||
/// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state
|
||||
/// from the widgets layer.
|
||||
enum AppLifecycleState {
|
||||
/// The application is visible and responsive to user input.
|
||||
///
|
||||
/// On Android, this state corresponds to the Flutter host view having focus
|
||||
/// ([`Activity.onWindowFocusChanged`](https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean))
|
||||
/// was called with true) while in Android's "resumed" state. It is possible
|
||||
/// for the Flutter app to be in the [inactive] state while still being in
|
||||
/// Android's
|
||||
/// ["onResume"](https://developer.android.com/guide/components/activities/activity-lifecycle)
|
||||
/// state if the app has lost focus
|
||||
/// ([`Activity.onWindowFocusChanged`](https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean))
|
||||
/// was called with false), but hasn't had
|
||||
/// [`Activity.onPause`](https://developer.android.com/reference/android/app/Activity#onPause())
|
||||
/// called on it.
|
||||
/// The application is visible and responding to user input.
|
||||
resumed,
|
||||
|
||||
/// The application is in an inactive state and is not receiving user input.
|
||||
///
|
||||
/// On iOS, this state corresponds to an app or the Flutter host view running
|
||||
/// in the foreground inactive state. Apps transition to this state when in a
|
||||
/// phone call, responding to a TouchID request, when entering the app
|
||||
/// in the foreground inactive state. Apps transition to this state when in
|
||||
/// a phone call, responding to a TouchID request, when entering the app
|
||||
/// switcher or the control center, or when the UIViewController hosting the
|
||||
/// Flutter app is transitioning.
|
||||
///
|
||||
/// On Android, this corresponds to an app or the Flutter host view running in
|
||||
/// Android's paused state (i.e.
|
||||
/// [`Activity.onPause`](https://developer.android.com/reference/android/app/Activity#onPause())
|
||||
/// has been called), or in Android's "resumed" state (i.e.
|
||||
/// [`Activity.onResume`](https://developer.android.com/reference/android/app/Activity#onResume())
|
||||
/// has been called) but it has lost window focus. Examples of when apps
|
||||
/// transition to this state include when the app is partially obscured or
|
||||
/// another activity is focused, such as: a split-screen app, a phone call, a
|
||||
/// picture-in-picture app, a system dialog, another view, when the
|
||||
/// notification window shade is down, or the application switcher is visible.
|
||||
/// On Android, this corresponds to an app or the Flutter host view running
|
||||
/// in the foreground inactive state. Apps transition to this state when
|
||||
/// another activity is focused, such as a split-screen app, a phone call,
|
||||
/// a picture-in-picture app, a system dialog, or another view.
|
||||
///
|
||||
/// Apps in this state should assume that they may be [paused] at any time.
|
||||
inactive,
|
||||
|
||||
@@ -157,12 +157,6 @@ public class FlutterActivity extends Activity
|
||||
eventDelegate.onUserLeaveHint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
eventDelegate.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
eventDelegate.onTrimMemory(level);
|
||||
|
||||
@@ -260,11 +260,6 @@ public final class FlutterActivityDelegate
|
||||
flutterView.getPluginRegistry().onUserLeaveHint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
flutterView.getPluginRegistry().onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
// Use a trim level delivered while the application is running so the
|
||||
|
||||
@@ -64,10 +64,4 @@ public interface FlutterActivityEvents
|
||||
|
||||
/** @see android.app.Activity#onUserLeaveHint() */
|
||||
void onUserLeaveHint();
|
||||
|
||||
/**
|
||||
* @param hasFocus True if the current activity window has focus.
|
||||
* @see android.app.Activity#onWindowFocusChanged(boolean)
|
||||
*/
|
||||
void onWindowFocusChanged(boolean hasFocus);
|
||||
}
|
||||
|
||||
@@ -155,12 +155,6 @@ public class FlutterFragmentActivity extends FragmentActivity
|
||||
eventDelegate.onUserLeaveHint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
eventDelegate.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
|
||||
@@ -28,7 +28,6 @@ public class FlutterPluginRegistry
|
||||
PluginRegistry.RequestPermissionsResultListener,
|
||||
PluginRegistry.ActivityResultListener,
|
||||
PluginRegistry.NewIntentListener,
|
||||
PluginRegistry.WindowFocusChangedListener,
|
||||
PluginRegistry.UserLeaveHintListener,
|
||||
PluginRegistry.ViewDestroyListener {
|
||||
private static final String TAG = "FlutterPluginRegistry";
|
||||
@@ -45,7 +44,6 @@ public class FlutterPluginRegistry
|
||||
private final List<ActivityResultListener> mActivityResultListeners = new ArrayList<>(0);
|
||||
private final List<NewIntentListener> mNewIntentListeners = new ArrayList<>(0);
|
||||
private final List<UserLeaveHintListener> mUserLeaveHintListeners = new ArrayList<>(0);
|
||||
private final List<WindowFocusChangedListener> mWindowFocusChangedListeners = new ArrayList<>(0);
|
||||
private final List<ViewDestroyListener> mViewDestroyListeners = new ArrayList<>(0);
|
||||
|
||||
public FlutterPluginRegistry(FlutterNativeView nativeView, Context context) {
|
||||
@@ -184,12 +182,6 @@ public class FlutterPluginRegistry
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registrar addWindowFocusChangedListener(WindowFocusChangedListener listener) {
|
||||
mWindowFocusChangedListeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registrar addViewDestroyListener(ViewDestroyListener listener) {
|
||||
mViewDestroyListeners.add(listener);
|
||||
@@ -235,13 +227,6 @@ public class FlutterPluginRegistry
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
for (WindowFocusChangedListener listener : mWindowFocusChangedListeners) {
|
||||
listener.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onViewDestroy(FlutterNativeView view) {
|
||||
boolean handled = false;
|
||||
|
||||
@@ -949,14 +949,6 @@ public class FlutterActivity extends Activity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (stillAttachedForEvent("onWindowFocusChanged")) {
|
||||
delegate.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
|
||||
@@ -581,7 +581,7 @@ import java.util.List;
|
||||
void onResume() {
|
||||
Log.v(TAG, "onResume()");
|
||||
ensureAlive();
|
||||
if (host.shouldDispatchAppLifecycleState() && flutterEngine != null) {
|
||||
if (host.shouldDispatchAppLifecycleState()) {
|
||||
flutterEngine.getLifecycleChannel().appIsResumed();
|
||||
}
|
||||
}
|
||||
@@ -629,7 +629,7 @@ import java.util.List;
|
||||
void onPause() {
|
||||
Log.v(TAG, "onPause()");
|
||||
ensureAlive();
|
||||
if (host.shouldDispatchAppLifecycleState() && flutterEngine != null) {
|
||||
if (host.shouldDispatchAppLifecycleState()) {
|
||||
flutterEngine.getLifecycleChannel().appIsInactive();
|
||||
}
|
||||
}
|
||||
@@ -652,7 +652,7 @@ import java.util.List;
|
||||
Log.v(TAG, "onStop()");
|
||||
ensureAlive();
|
||||
|
||||
if (host.shouldDispatchAppLifecycleState() && flutterEngine != null) {
|
||||
if (host.shouldDispatchAppLifecycleState()) {
|
||||
flutterEngine.getLifecycleChannel().appIsPaused();
|
||||
}
|
||||
|
||||
@@ -763,7 +763,7 @@ import java.util.List;
|
||||
platformPlugin = null;
|
||||
}
|
||||
|
||||
if (host.shouldDispatchAppLifecycleState() && flutterEngine != null) {
|
||||
if (host.shouldDispatchAppLifecycleState()) {
|
||||
flutterEngine.getLifecycleChannel().appIsDetached();
|
||||
}
|
||||
|
||||
@@ -898,27 +898,6 @@ import java.util.List;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke this from {@code Activity#onWindowFocusChanged()}.
|
||||
*
|
||||
* <p>A {@code Fragment} host must have its containing {@code Activity} forward this call so that
|
||||
* the {@code Fragment} can then invoke this method.
|
||||
*/
|
||||
void onWindowFocusChanged(boolean hasFocus) {
|
||||
ensureAlive();
|
||||
Log.v(TAG, "Received onWindowFocusChanged: " + (hasFocus ? "true" : "false"));
|
||||
if (host.shouldDispatchAppLifecycleState() && flutterEngine != null) {
|
||||
// TODO(gspencergoog): Once we have support for multiple windows/views,
|
||||
// this code will need to consult the list of windows/views to determine if
|
||||
// any windows in the app are focused and call the appropriate function.
|
||||
if (hasFocus) {
|
||||
flutterEngine.getLifecycleChannel().aWindowIsFocused();
|
||||
} else {
|
||||
flutterEngine.getLifecycleChannel().noWindowsAreFocused();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke this from {@link android.app.Activity#onTrimMemory(int)}.
|
||||
*
|
||||
|
||||
@@ -8,16 +8,13 @@ import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver.OnWindowFocusChangeListener;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
@@ -170,19 +167,6 @@ public class FlutterFragment extends Fragment
|
||||
protected static final String ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED =
|
||||
"should_automatically_handle_on_back_pressed";
|
||||
|
||||
@RequiresApi(18)
|
||||
private final OnWindowFocusChangeListener onWindowFocusChangeListener =
|
||||
Build.VERSION.SDK_INT >= 18
|
||||
? new OnWindowFocusChangeListener() {
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (stillAttachedForEvent("onWindowFocusChanged")) {
|
||||
delegate.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Creates a {@code FlutterFragment} with a default configuration.
|
||||
*
|
||||
@@ -1125,23 +1109,9 @@ public class FlutterFragment extends Fragment
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
view.getViewTreeObserver().addOnWindowFocusChangeListener(onWindowFocusChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
// onWindowFocusChangeListener is API 18+ only.
|
||||
requireView()
|
||||
.getViewTreeObserver()
|
||||
.removeOnWindowFocusChangeListener(onWindowFocusChangeListener);
|
||||
}
|
||||
if (stillAttachedForEvent("onDestroyView")) {
|
||||
delegate.onDestroyView();
|
||||
}
|
||||
|
||||
@@ -732,10 +732,6 @@ import java.util.Set;
|
||||
private final Set<io.flutter.plugin.common.PluginRegistry.UserLeaveHintListener>
|
||||
onUserLeaveHintListeners = new HashSet<>();
|
||||
|
||||
@NonNull
|
||||
private final Set<io.flutter.plugin.common.PluginRegistry.WindowFocusChangedListener>
|
||||
onWindowFocusChangedListeners = new HashSet<>();
|
||||
|
||||
@NonNull
|
||||
private final Set<OnSaveInstanceStateListener> onSaveInstanceStateListeners = new HashSet<>();
|
||||
|
||||
@@ -851,25 +847,6 @@ import java.util.Set;
|
||||
onUserLeaveHintListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnWindowFocusChangedListener(
|
||||
@NonNull io.flutter.plugin.common.PluginRegistry.WindowFocusChangedListener listener) {
|
||||
onWindowFocusChangedListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOnWindowFocusChangedListener(
|
||||
@NonNull io.flutter.plugin.common.PluginRegistry.WindowFocusChangedListener listener) {
|
||||
onWindowFocusChangedListeners.remove(listener);
|
||||
}
|
||||
|
||||
void onWindowFocusChanged(boolean hasFocus) {
|
||||
for (io.flutter.plugin.common.PluginRegistry.WindowFocusChangedListener listener :
|
||||
onWindowFocusChangedListeners) {
|
||||
listener.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnSaveStateListener(@NonNull OnSaveInstanceStateListener listener) {
|
||||
onSaveInstanceStateListeners.add(listener);
|
||||
|
||||
@@ -91,19 +91,6 @@ public interface ActivityPluginBinding {
|
||||
*/
|
||||
void removeOnUserLeaveHintListener(@NonNull PluginRegistry.UserLeaveHintListener listener);
|
||||
|
||||
/**
|
||||
* Adds a listener that is invoked whenever the associated {@link android.app.Activity}'s {@code
|
||||
* onWindowFocusChanged()} method is invoked.
|
||||
*/
|
||||
void addOnWindowFocusChangedListener(@NonNull PluginRegistry.WindowFocusChangedListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener that was added in {@link
|
||||
* #addOnWindowFocusChangedListener(PluginRegistry.WindowFocusChangedListener)}.
|
||||
*/
|
||||
void removeOnWindowFocusChangedListener(
|
||||
@NonNull PluginRegistry.WindowFocusChangedListener listener);
|
||||
|
||||
/**
|
||||
* Adds a listener that is invoked when the associated {@code Activity} or {@code Fragment} saves
|
||||
* and restores instance state.
|
||||
|
||||
@@ -39,8 +39,6 @@ class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, Activity
|
||||
new HashSet<>();
|
||||
private final Set<PluginRegistry.NewIntentListener> newIntentListeners = new HashSet<>();
|
||||
private final Set<PluginRegistry.UserLeaveHintListener> userLeaveHintListeners = new HashSet<>();
|
||||
private final Set<PluginRegistry.WindowFocusChangedListener> WindowFocusChangedListeners =
|
||||
new HashSet<>();
|
||||
private FlutterPlugin.FlutterPluginBinding pluginBinding;
|
||||
private ActivityPluginBinding activityPluginBinding;
|
||||
|
||||
@@ -148,18 +146,6 @@ class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, Activity
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginRegistry.Registrar addWindowFocusChangedListener(
|
||||
PluginRegistry.WindowFocusChangedListener listener) {
|
||||
WindowFocusChangedListeners.add(listener);
|
||||
|
||||
if (activityPluginBinding != null) {
|
||||
activityPluginBinding.addOnWindowFocusChangedListener(listener);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public PluginRegistry.Registrar addViewDestroyListener(
|
||||
@@ -227,8 +213,5 @@ class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, Activity
|
||||
for (PluginRegistry.UserLeaveHintListener listener : userLeaveHintListeners) {
|
||||
activityPluginBinding.addOnUserLeaveHintListener(listener);
|
||||
}
|
||||
for (PluginRegistry.WindowFocusChangedListener listener : WindowFocusChangedListeners) {
|
||||
activityPluginBinding.addOnWindowFocusChangedListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,106 +5,39 @@
|
||||
package io.flutter.embedding.engine.systemchannels;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.plugin.common.BasicMessageChannel;
|
||||
import io.flutter.plugin.common.StringCodec;
|
||||
|
||||
/**
|
||||
* A {@link BasicMessageChannel} that communicates lifecycle events to the framework.
|
||||
*
|
||||
* <p>The activity listens to the Android lifecycle events, in addition to the focus events for
|
||||
* windows, and this channel combines that information to decide if the application is the inactive,
|
||||
* resumed, paused, or detached state.
|
||||
*/
|
||||
/** TODO(mattcarroll): fill in javadoc for LifecycleChannel. */
|
||||
public class LifecycleChannel {
|
||||
private static final String TAG = "LifecycleChannel";
|
||||
private static final String CHANNEL_NAME = "flutter/lifecycle";
|
||||
|
||||
// These should stay in sync with the AppLifecycleState enum in the framework.
|
||||
private static final String RESUMED = "AppLifecycleState.resumed";
|
||||
private static final String INACTIVE = "AppLifecycleState.inactive";
|
||||
private static final String PAUSED = "AppLifecycleState.paused";
|
||||
private static final String DETACHED = "AppLifecycleState.detached";
|
||||
|
||||
private String lastAndroidState = "";
|
||||
private String lastFlutterState = "";
|
||||
private boolean lastFocus = false;
|
||||
|
||||
@NonNull private final BasicMessageChannel<String> channel;
|
||||
@NonNull public final BasicMessageChannel<String> channel;
|
||||
|
||||
public LifecycleChannel(@NonNull DartExecutor dartExecutor) {
|
||||
this(new BasicMessageChannel<String>(dartExecutor, CHANNEL_NAME, StringCodec.INSTANCE));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public LifecycleChannel(@NonNull BasicMessageChannel<String> channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
// Here's the state table this implements:
|
||||
//
|
||||
// | Android State | Window focused | Flutter state |
|
||||
// |---------------|----------------|---------------|
|
||||
// | Resumed | true | resumed |
|
||||
// | Resumed | false | inactive |
|
||||
// | Paused | true | inactive |
|
||||
// | Paused | false | inactive |
|
||||
// | Stopped | true | paused |
|
||||
// | Stopped | false | paused |
|
||||
// | Detached | true | detached |
|
||||
// | Detached | false | detached |
|
||||
private void sendState(String state, boolean hasFocus) {
|
||||
if (lastAndroidState == state && hasFocus == lastFocus) {
|
||||
// No inputs changed, so Flutter state could not have changed.
|
||||
return;
|
||||
}
|
||||
String newState;
|
||||
if (state == RESUMED) {
|
||||
// Focus is only taken into account when the Android state is "Resumed".
|
||||
// In all other states, focus is ignored, because we can't know what order
|
||||
// Android lifecycle notifications and window focus notifications events
|
||||
// will arrive in, and those states don't send input events anyhow.
|
||||
newState = hasFocus ? RESUMED : INACTIVE;
|
||||
} else {
|
||||
newState = state;
|
||||
}
|
||||
// Keep the last reported values for future updates.
|
||||
lastAndroidState = state;
|
||||
lastFocus = hasFocus;
|
||||
if (newState == lastFlutterState) {
|
||||
// No change in the resulting Flutter state, so don't report anything.
|
||||
return;
|
||||
}
|
||||
Log.v(TAG, "Sending " + newState + " message.");
|
||||
channel.send(newState);
|
||||
lastFlutterState = newState;
|
||||
}
|
||||
|
||||
// Called if at least one window in the app has focus.
|
||||
public void aWindowIsFocused() {
|
||||
sendState(lastAndroidState, true);
|
||||
}
|
||||
|
||||
// Called if no windows in the app have focus.
|
||||
public void noWindowsAreFocused() {
|
||||
sendState(lastAndroidState, false);
|
||||
}
|
||||
|
||||
public void appIsResumed() {
|
||||
sendState(RESUMED, lastFocus);
|
||||
this.channel =
|
||||
new BasicMessageChannel<>(dartExecutor, "flutter/lifecycle", StringCodec.INSTANCE);
|
||||
}
|
||||
|
||||
public void appIsInactive() {
|
||||
sendState(INACTIVE, lastFocus);
|
||||
Log.v(TAG, "Sending AppLifecycleState.inactive message.");
|
||||
channel.send("AppLifecycleState.inactive");
|
||||
}
|
||||
|
||||
public void appIsResumed() {
|
||||
Log.v(TAG, "Sending AppLifecycleState.resumed message.");
|
||||
channel.send("AppLifecycleState.resumed");
|
||||
}
|
||||
|
||||
public void appIsPaused() {
|
||||
sendState(PAUSED, lastFocus);
|
||||
Log.v(TAG, "Sending AppLifecycleState.paused message.");
|
||||
channel.send("AppLifecycleState.paused");
|
||||
}
|
||||
|
||||
public void appIsDetached() {
|
||||
sendState(DETACHED, lastFocus);
|
||||
Log.v(TAG, "Sending AppLifecycleState.detached message.");
|
||||
channel.send("AppLifecycleState.detached");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,23 +309,6 @@ public interface PluginRegistry {
|
||||
@NonNull
|
||||
Registrar addUserLeaveHintListener(@NonNull UserLeaveHintListener listener);
|
||||
|
||||
/**
|
||||
* Adds a callback allowing the plugin to take part in handling incoming calls to {@link
|
||||
* Activity#onWindowFocusChanged(boolean)}.
|
||||
*
|
||||
* <p>This registrar is for Flutter's v1 embedding. To listen for leave hints in the v2
|
||||
* embedding, use {@link
|
||||
* ActivityPluginBinding#addOnWindowFocusChangedListener(PluginRegistry.WindowFocusChangedListener)}.
|
||||
*
|
||||
* <p>For instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit
|
||||
* http://flutter.dev/go/android-plugin-migration
|
||||
*
|
||||
* @param listener a {@link WindowFocusChangedListener} callback.
|
||||
* @return this {@link Registrar}.
|
||||
*/
|
||||
@NonNull
|
||||
Registrar addWindowFocusChangedListener(@NonNull WindowFocusChangedListener listener);
|
||||
|
||||
/**
|
||||
* Adds a callback allowing the plugin to take part in handling incoming calls to {@link
|
||||
* Activity#onDestroy()}.
|
||||
@@ -405,14 +388,6 @@ public interface PluginRegistry {
|
||||
void onUserLeaveHint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate interface for handling window focus changes on behalf of the main {@link
|
||||
* android.app.Activity}.
|
||||
*/
|
||||
interface WindowFocusChangedListener {
|
||||
void onWindowFocusChanged(boolean hasFocus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate interface for handling an {@link android.app.Activity}'s onDestroy method being
|
||||
* called. A plugin that implements this interface can adopt the {@link FlutterNativeView} by
|
||||
|
||||
@@ -107,36 +107,13 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// By the time an Activity/Fragment is started, we don't expect any lifecycle messages
|
||||
// to have been sent to Flutter.
|
||||
delegate.onStart();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the Activity/Fragment is resumed, a resumed message should have been sent to Flutter.
|
||||
delegate.onResume();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the app loses focus because something else has it (e.g. notification
|
||||
// windowshade or app switcher), it should go to inactive.
|
||||
delegate.onWindowFocusChanged(false);
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
|
||||
|
||||
// When the app regains focus, it should go to resumed again.
|
||||
delegate.onWindowFocusChanged(true);
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
@@ -144,8 +121,6 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// When the Activity/Fragment is paused, an inactive message should have been sent to Flutter.
|
||||
delegate.onPause();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
@@ -155,8 +130,6 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
// Notice that Flutter uses the term "paused" in a different way, and at a different time
|
||||
// than the Android OS.
|
||||
delegate.onStop();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
|
||||
@@ -164,8 +137,6 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
|
||||
// When activity detaches, a detached message should have been sent to Flutter.
|
||||
delegate.onDetach();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
|
||||
@@ -186,14 +157,10 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
delegate.onCreateView(null, null, null, 0, true);
|
||||
delegate.onStart();
|
||||
delegate.onResume();
|
||||
delegate.onWindowFocusChanged(false);
|
||||
delegate.onWindowFocusChanged(true);
|
||||
delegate.onPause();
|
||||
delegate.onStop();
|
||||
delegate.onDetach();
|
||||
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
|
||||
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
package io.flutter.embedding.engine.systemchannels;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.plugin.common.BasicMessageChannel;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@Config(manifest = Config.NONE)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LifecycleChannelTest {
|
||||
LifecycleChannel lifecycleChannel;
|
||||
BasicMessageChannel<String> mockChannel;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mockChannel = mock(BasicMessageChannel.class);
|
||||
lifecycleChannel = new LifecycleChannel(mockChannel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lifecycleChannel_handlesResumed() {
|
||||
lifecycleChannel.appIsResumed();
|
||||
ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(1)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.inactive", stringArgumentCaptor.getValue());
|
||||
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(2)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.resumed", stringArgumentCaptor.getValue());
|
||||
|
||||
lifecycleChannel.noWindowsAreFocused();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(3)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.inactive", stringArgumentCaptor.getValue());
|
||||
|
||||
// Stays inactive, so no event is sent.
|
||||
lifecycleChannel.appIsInactive();
|
||||
verify(mockChannel, times(3)).send(any(String.class));
|
||||
|
||||
// Stays inactive, so no event is sent.
|
||||
lifecycleChannel.appIsResumed();
|
||||
verify(mockChannel, times(3)).send(any(String.class));
|
||||
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(4)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.resumed", stringArgumentCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lifecycleChannel_handlesInactive() {
|
||||
lifecycleChannel.appIsInactive();
|
||||
ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(1)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.inactive", stringArgumentCaptor.getValue());
|
||||
|
||||
// Stays inactive, so no event is sent.
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
verify(mockChannel, times(1)).send(any(String.class));
|
||||
|
||||
// Stays inactive, so no event is sent.
|
||||
lifecycleChannel.noWindowsAreFocused();
|
||||
verify(mockChannel, times(1)).send(any(String.class));
|
||||
|
||||
lifecycleChannel.appIsResumed();
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(2)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.resumed", stringArgumentCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lifecycleChannel_handlesPaused() {
|
||||
// Stays inactive, so no event is sent.
|
||||
lifecycleChannel.appIsPaused();
|
||||
ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(1)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.paused", stringArgumentCaptor.getValue());
|
||||
|
||||
// Stays paused, so no event is sent.
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
verify(mockChannel, times(1)).send(any(String.class));
|
||||
|
||||
lifecycleChannel.noWindowsAreFocused();
|
||||
verify(mockChannel, times(1)).send(any(String.class));
|
||||
|
||||
lifecycleChannel.appIsResumed();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(2)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.inactive", stringArgumentCaptor.getValue());
|
||||
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(3)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.resumed", stringArgumentCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lifecycleChannel_handlesDetached() {
|
||||
// Stays inactive, so no event is sent.
|
||||
lifecycleChannel.appIsDetached();
|
||||
ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(1)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.detached", stringArgumentCaptor.getValue());
|
||||
|
||||
// Stays paused, so no event is sent.
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
verify(mockChannel, times(1)).send(any(String.class));
|
||||
|
||||
lifecycleChannel.noWindowsAreFocused();
|
||||
verify(mockChannel, times(1)).send(any(String.class));
|
||||
|
||||
lifecycleChannel.appIsResumed();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(2)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.inactive", stringArgumentCaptor.getValue());
|
||||
|
||||
lifecycleChannel.aWindowIsFocused();
|
||||
stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(mockChannel, times(3)).send(stringArgumentCaptor.capture());
|
||||
assertEquals("AppLifecycleState.resumed", stringArgumentCaptor.getValue());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user