[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:
Xilai Zhang
2023-05-01 09:46:02 -07:00
committed by GitHub
parent 159f303b92
commit e49577708d
16 changed files with 33 additions and 471 deletions

View File

@@ -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,

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)}.
*

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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.

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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());
}
}