Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Support basic back navigation in Android 13/API 33 #35678

Merged
merged 12 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
Expand Down Expand Up @@ -495,13 +497,57 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {

lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should existing use of lifecycle change to getLifecycle()? Otherwise it seems like we're introducing the possibility of NPEs.


registerOnBackInvokedCallback();

configureWindowForTransparency();

setContentView(createFlutterView());

configureStatusBarForFullscreenFlutterExperience();
}

/**
* Registers the callback with OnBackInvokedDispatcher to capture back navigation gestures and
* pass them to the framework.
*
* <p>This replaces the deprecated onBackPressed method override in order to support API 33's
* predictive back navigation feature.
*
* <p>The callback must be unregistered in order to prevent unpredictable behavior once outside
* the Flutter app.
*/
@VisibleForTesting
public void registerOnBackInvokedCallback() {
if (Build.VERSION.SDK_INT >= 33) {
getOnBackInvokedDispatcher()
.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback);
}
}

/**
* Unregisters the callback from OnBackInvokedDispatcher.
*
* <p>This should be called when the activity is no longer in use to prevent unpredictable
* behavior such as being stuck and unable to press back.
*/
@VisibleForTesting
public void unregisterOnBackInvokedCallback() {
if (Build.VERSION.SDK_INT >= 33) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback);
}
}

private final OnBackInvokedCallback onBackInvokedCallback =
Build.VERSION.SDK_INT >= 33
? new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
onBackPressed();
}
}
: null;

/**
* Switches themes for this {@code Activity} from the theme used to launch this {@code Activity}
* to a "normal theme" that is intended for regular {@code Activity} operation.
Expand Down Expand Up @@ -680,7 +726,9 @@ protected void onSaveInstanceState(Bundle outState) {
*
* <p>After calling, this activity should be disposed immediately and not be re-used.
*/
private void release() {
@VisibleForTesting
public void release() {
unregisterOnBackInvokedCallback();
if (delegate != null) {
delegate.release();
delegate = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,36 @@ public void flutterViewHasId() {
assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView);
}

// TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric
// test that directly exercises the OnBackInvoked APIs when API 33 is supported.
@Test
@TargetApi(33)
public void itRegistersOnBackInvokedCallbackOnCreate() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we test unregistration as well?

Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
ActivityController<FlutterActivityWithReportFullyDrawn> activityController =
Robolectric.buildActivity(FlutterActivityWithReportFullyDrawn.class, intent);
FlutterActivityWithReportFullyDrawn activity = spy(activityController.get());

activity.onCreate(null);

verify(activity, times(1)).registerOnBackInvokedCallback();
}

// TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric
// test that directly exercises the OnBackInvoked APIs when API 33 is supported.
@Test
@TargetApi(33)
public void itUnregistersOnBackInvokedCallbackOnRelease() {
Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
ActivityController<FlutterActivityWithReportFullyDrawn> activityController =
Robolectric.buildActivity(FlutterActivityWithReportFullyDrawn.class, intent);
FlutterActivityWithReportFullyDrawn activity = spy(activityController.get());

activity.release();

verify(activity, times(1)).unregisterOnBackInvokedCallback();
}

@Test
public void itCreatesDefaultIntentWithExpectedDefaults() {
Intent intent = FlutterActivity.createDefaultIntent(ctx);
Expand Down Expand Up @@ -596,6 +626,14 @@ public void resetFullyDrawn() {
}
}

private class FlutterActivityWithMockBackInvokedHandling extends FlutterActivity {
@Override
public void registerOnBackInvokedCallback() {}

@Override
public void unregisterOnBackInvokedCallback() {}
}

private static final class FakeFlutterPlugin
implements FlutterPlugin,
ActivityAware,
Expand Down
6 changes: 3 additions & 3 deletions shell/platform/android/test_runner/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ android {
testImplementation "com.google.android.play:core:1.8.0"
testImplementation "com.ibm.icu:icu4j:69.1"
testImplementation "org.robolectric:robolectric:4.7.3"
testImplementation "junit:junit:4.13"
testImplementation "androidx.test.ext:junit:1.1.3"
testImplementation "junit:junit:4.13.2"
testImplementation "androidx.test.ext:junit:1.1.4-alpha07"

def mockitoVersion = "4.1.0"
def mockitoVersion = "4.7.0"
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito:mockito-inline:$mockitoVersion"
testImplementation "org.mockito:mockito-android:$mockitoVersion"
Expand Down