Migrate from FragmentActivity overloads to new Menu API
Migrating the use of FragmentActivity's menu overloads
and instead using the new Menu APIs
RelNote: "`FragmentActivity` no longer relies on menu
overloads and instead uses the new Menu APIs."
Test: ComponentActivityMenuTest
Change-Id: I2075845d3598bf3b2635512918fca04abe2e281a
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index b273407..b17e6f0 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -204,16 +204,16 @@
method @Deprecated public void dispatchConfigurationChanged(android.content.res.Configuration);
method public boolean dispatchContextItemSelected(android.view.MenuItem);
method public void dispatchCreate();
- method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method @Deprecated public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
method public void dispatchDestroy();
method public void dispatchDestroyView();
method @Deprecated public void dispatchLowMemory();
method @Deprecated public void dispatchMultiWindowModeChanged(boolean);
- method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
- method public void dispatchOptionsMenuClosed(android.view.Menu);
+ method @Deprecated public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+ method @Deprecated public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
method @Deprecated public void dispatchPictureInPictureModeChanged(boolean);
- method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+ method @Deprecated public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method @Deprecated public void dispatchReallyStop();
method public void dispatchResume();
method public void dispatchStart();
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index b273407..b17e6f0 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -204,16 +204,16 @@
method @Deprecated public void dispatchConfigurationChanged(android.content.res.Configuration);
method public boolean dispatchContextItemSelected(android.view.MenuItem);
method public void dispatchCreate();
- method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method @Deprecated public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
method public void dispatchDestroy();
method public void dispatchDestroyView();
method @Deprecated public void dispatchLowMemory();
method @Deprecated public void dispatchMultiWindowModeChanged(boolean);
- method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
- method public void dispatchOptionsMenuClosed(android.view.Menu);
+ method @Deprecated public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+ method @Deprecated public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
method @Deprecated public void dispatchPictureInPictureModeChanged(boolean);
- method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+ method @Deprecated public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method @Deprecated public void dispatchReallyStop();
method public void dispatchResume();
method public void dispatchStart();
diff --git a/fragment/fragment/api/restricted_current.ignore b/fragment/fragment/api/restricted_current.ignore
index 5135e70..064bc98 100644
--- a/fragment/fragment/api/restricted_current.ignore
+++ b/fragment/fragment/api/restricted_current.ignore
@@ -1,4 +1,8 @@
// Baseline format: 1.0
+RemovedDeprecatedMethod: androidx.fragment.app.FragmentActivity#onPrepareOptionsPanel(android.view.View, android.view.Menu):
+ Removed deprecated method androidx.fragment.app.FragmentActivity.onPrepareOptionsPanel(android.view.View,android.view.Menu)
+
+
RemovedMethod: androidx.fragment.app.FragmentActivity#onMultiWindowModeChanged(boolean):
Removed method androidx.fragment.app.FragmentActivity.onMultiWindowModeChanged(boolean)
RemovedMethod: androidx.fragment.app.FragmentActivity#onPictureInPictureModeChanged(boolean):
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 3df729d..20ef945 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -173,7 +173,6 @@
method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
method @Deprecated @MainThread public void onAttachFragment(androidx.fragment.app.Fragment);
- method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected boolean onPrepareOptionsPanel(android.view.View?, android.view.Menu);
method protected void onResumeFragments();
method public void onStateNotSaved();
method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
@@ -209,16 +208,16 @@
method @Deprecated public void dispatchConfigurationChanged(android.content.res.Configuration);
method public boolean dispatchContextItemSelected(android.view.MenuItem);
method public void dispatchCreate();
- method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method @Deprecated public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
method public void dispatchDestroy();
method public void dispatchDestroyView();
method @Deprecated public void dispatchLowMemory();
method @Deprecated public void dispatchMultiWindowModeChanged(boolean);
- method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
- method public void dispatchOptionsMenuClosed(android.view.Menu);
+ method @Deprecated public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+ method @Deprecated public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
method @Deprecated public void dispatchPictureInPictureModeChanged(boolean);
- method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+ method @Deprecated public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method @Deprecated public void dispatchReallyStop();
method public void dispatchResume();
method public void dispatchStart();
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
index 68affee..a24fbf3 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
@@ -1162,6 +1162,7 @@
@Test
@UiThreadTest
+ @Suppress("DEPRECATION")
fun optionsMenu() {
val viewModelStore = ViewModelStore()
val fc = activityRule.startupFragmentController(viewModelStore)
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt
index a06ae0b..a4c3d0d 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OptionsMenuFragmentTest.kt
@@ -25,16 +25,21 @@
import androidx.fragment.app.test.FragmentTestActivity
import androidx.fragment.test.R
import androidx.test.core.app.ActivityScenario
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.assertion.ViewAssertions
+import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertWithMessage
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -90,7 +95,7 @@
.commitNow()
}
- assertWithMessage("conCreateOptions menu was not called")
+ assertWithMessage("onCreateOptionsMenu was not called")
.that(fragment.onCreateOptionsMenuCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
.isTrue()
}
@@ -240,9 +245,80 @@
.that(parent.mChildFragmentManager.checkForMenus()).isTrue()
}
+ @Test
+ fun onPrepareOptionsMenu() {
+ activityRule.setContentView(R.layout.simple_container)
+ val fragment = MenuFragment()
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ openActionBarOverflowOrOptionsMenu(activityRule.activity.applicationContext)
+ onView(ViewMatchers.withText("Item1")).perform(ViewActions.click())
+ assertWithMessage("onPrepareOptionsMenu was not called")
+ .that(fragment.onPrepareOptionsMenuCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
+ }
+
+ @Test
+ fun inflatesMenu() {
+ activityRule.setContentView(R.layout.simple_container)
+ val fragment = MenuFragment()
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ openActionBarOverflowOrOptionsMenu(activityRule.activity.applicationContext)
+ onView(ViewMatchers.withText("Item1"))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ onView(ViewMatchers.withText("Item2"))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
+ }
+
+ @Test
+ fun menuItemSelected() {
+ activityRule.setContentView(R.layout.simple_container)
+ val fragment = MenuFragment()
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ openActionBarOverflowOrOptionsMenu(activityRule.activity.applicationContext)
+ onView(ViewMatchers.withText("Item1")).perform(ViewActions.click())
+
+ openActionBarOverflowOrOptionsMenu(activityRule.activity.applicationContext)
+ onView(ViewMatchers.withText("Item2")).perform(ViewActions.click())
+ }
+
+ @Test
+ fun onOptionsMenuClosed() {
+ activityRule.setContentView(R.layout.simple_container)
+ val fragment = MenuFragment()
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ openActionBarOverflowOrOptionsMenu(activityRule.activity.applicationContext)
+ activityRule.runOnUiThread {
+ activityRule.activity.closeOptionsMenu()
+ }
+ assertWithMessage("onOptionsMenuClosed was not called")
+ .that(fragment.onOptionsMenuClosedCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
+ }
+
class MenuFragment : StrictViewFragment(R.layout.fragment_a) {
var >
val >
+ val >
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -259,6 +335,11 @@
super.onPrepareOptionsMenu(menu)
onPrepareOptionsMenuCountDownLatch.countDown()
}
+
+ override fun onOptionsMenuClosed(menu: Menu) {
+ super.onOptionsMenuClosed(menu)
+ onOptionsMenuClosedCountDownLatch.countDown()
+ }
}
class ParentOptionsMenuFragment(
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
index 1467fb5..b48c19c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -17,7 +17,6 @@
package androidx.fragment.app;
import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -29,7 +28,6 @@
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
-import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
@@ -47,7 +45,6 @@
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
import androidx.core.app.ActivityCompat;
import androidx.core.app.MultiWindowModeChangedInfo;
import androidx.core.app.OnMultiWindowModeChangedProvider;
@@ -57,6 +54,8 @@
import androidx.core.content.OnConfigurationChangedProvider;
import androidx.core.content.OnTrimMemoryProvider;
import androidx.core.util.Consumer;
+import androidx.core.view.MenuHost;
+import androidx.core.view.MenuProvider;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
@@ -221,21 +220,6 @@
mFragments.dispatchCreate();
}
- /**
- * {@inheritDoc}
- *
- * Dispatch to Fragment.onCreateOptionsMenu().
- */
- @Override
- public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- boolean show = super.onCreatePanelMenu(featureId, menu);
- show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
- return show;
- }
- return super.onCreatePanelMenu(featureId, menu);
- }
-
@Override
@Nullable
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
@@ -276,40 +260,17 @@
mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
- /**
- * {@inheritDoc}
- *
- * Dispatch context and options menu to fragments.
- */
@Override
public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
return true;
}
- switch (featureId) {
- case Window.FEATURE_OPTIONS_PANEL:
- return mFragments.dispatchOptionsItemSelected(item);
-
- case Window.FEATURE_CONTEXT_MENU:
- return mFragments.dispatchContextItemSelected(item);
-
- default:
- return false;
+ if (featureId == Window.FEATURE_CONTEXT_MENU) {
+ return mFragments.dispatchContextItemSelected(item);
}
- }
- /**
- * {@inheritDoc}
- *
- * Call onOptionsMenuClosed() on fragments.
- */
- @Override
- public void onPanelClosed(int featureId, @NonNull Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- mFragments.dispatchOptionsMenuClosed(menu);
- }
- super.onPanelClosed(featureId, menu);
+ return false;
}
/**
@@ -376,33 +337,6 @@
/**
* {@inheritDoc}
*
- * Dispatch onPrepareOptionsMenu() to fragments.
- */
- @SuppressWarnings("deprecation")
- @Override
- public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- boolean goforit = onPrepareOptionsPanel(view, menu);
- goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
- return goforit;
- }
- return super.onPreparePanel(featureId, view, menu);
- }
-
- /**
- * @hide
- * @deprecated Override {@link #onPreparePanel(int, View, Menu)}.
- */
- @SuppressWarnings("DeprecatedIsStillUsed")
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- @Deprecated
- protected boolean onPrepareOptionsPanel(@Nullable View view, @NonNull Menu menu) {
- return super.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, view, menu);
- }
-
- /**
- * {@inheritDoc}
- *
* Dispatch onStart() to all fragments.
*/
@Override
@@ -645,7 +579,9 @@
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
SavedStateRegistryOwner,
- FragmentOnAttachListener {
+ FragmentOnAttachListener,
+ MenuHost {
+
public HostCallbacks() {
super(FragmentActivity.this /*fragmentActivity*/);
}
@@ -696,7 +632,7 @@
@Override
public void onSupportInvalidateOptionsMenu() {
- FragmentActivity.this.supportInvalidateOptionsMenu();
+ invalidateMenu();
}
@Override
@@ -794,6 +730,32 @@
@NonNull Consumer<PictureInPictureModeChangedInfo> listener) {
FragmentActivity.this.removeOnPictureInPictureModeChangedListener(listener);
}
+
+ @Override
+ public void addMenuProvider(@NonNull MenuProvider provider) {
+ FragmentActivity.this.addMenuProvider(provider);
+ }
+
+ @Override
+ public void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner) {
+ FragmentActivity.this.addMenuProvider(provider, owner);
+ }
+
+ @Override
+ public void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner,
+ @NonNull Lifecycle.State state) {
+ FragmentActivity.this.addMenuProvider(provider, owner, state);
+ }
+
+ @Override
+ public void removeMenuProvider(@NonNull MenuProvider provider) {
+ FragmentActivity.this.removeMenuProvider(provider);
+ }
+
+ @Override
+ public void invalidateMenu() {
+ FragmentActivity.this.invalidateOptionsMenu();
+ }
}
void markFragmentsCreated() {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java
index 9a5ecfa..fcbdcfc 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentController.java
@@ -32,6 +32,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.SimpleArrayMap;
+import androidx.core.view.MenuHost;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.loader.app.LoaderManager;
@@ -413,7 +414,11 @@
*
* @return {@code true} if the options menu contains items to display
* @see Fragment#onCreateOptionsMenu(Menu, MenuInflater)
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement
+ * {@link MenuHost} to automatically dispatch menu changes to fragments.
*/
+ @Deprecated
public boolean dispatchCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
return mHost.mFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
}
@@ -425,7 +430,11 @@
*
* @return {@code true} if the options menu contains items to display
* @see Fragment#onPrepareOptionsMenu(Menu)
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement
+ * {@link MenuHost} to automatically dispatch menu changes to fragments.
*/
+ @Deprecated
public boolean dispatchPrepareOptionsMenu(@NonNull Menu menu) {
return mHost.mFragmentManager.dispatchPrepareOptionsMenu(menu);
}
@@ -438,7 +447,11 @@
*
* @return {@code true} if the options menu selection event was consumed
* @see Fragment#onOptionsItemSelected(MenuItem)
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement
+ * {@link MenuHost} to automatically dispatch menu changes to fragments.
*/
+ @Deprecated
public boolean dispatchOptionsItemSelected(@NonNull MenuItem item) {
return mHost.mFragmentManager.dispatchOptionsItemSelected(item);
}
@@ -462,7 +475,11 @@
* <p>Call immediately after closing the Fragment's options menu.
*
* @see Fragment#onOptionsMenuClosed(Menu)
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement
+ * {@link MenuHost} to automatically dispatch menu changes to fragments.
*/
+ @Deprecated
public void dispatchOptionsMenuClosed(@NonNull Menu menu) {
mHost.mFragmentManager.dispatchOptionsMenuClosed(menu);
}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 89fc13c..c71f10a 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -67,6 +67,8 @@
import androidx.core.content.OnConfigurationChangedProvider;
import androidx.core.content.OnTrimMemoryProvider;
import androidx.core.util.Consumer;
+import androidx.core.view.MenuHost;
+import androidx.core.view.MenuProvider;
import androidx.fragment.R;
import androidx.fragment.app.strictmode.FragmentStrictMode;
import androidx.lifecycle.Lifecycle;
@@ -454,6 +456,28 @@
mOnPictureInPictureModeChangedListener = info -> dispatchPictureInPictureModeChanged(
info.isInPictureInPictureMode());
+ private final MenuProvider mMenuProvider = new MenuProvider() {
+ @Override
+ public void onPrepareMenu(@NonNull Menu menu) {
+ dispatchPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
+ dispatchCreateOptionsMenu(menu, menuInflater);
+ }
+
+ @Override
+ public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
+ return dispatchOptionsItemSelected(menuItem);
+ }
+
+ @Override
+ public void onMenuClosed(@NonNull Menu menu) {
+ dispatchOptionsMenuClosed(menu);
+ }
+ };
+
int mCurState = Fragment.INITIALIZING;
private FragmentHostCallback<?> mHost;
private FragmentContainer mContainer;
@@ -2729,6 +2753,10 @@
onPictureInPictureModeChangedProvider.addOnPictureInPictureModeChangedListener(
mOnPictureInPictureModeChangedListener);
}
+
+ if (mHost instanceof MenuHost && parent == null) {
+ ((MenuHost) mHost).addMenuProvider(mMenuProvider);
+ }
}
void noteStateNotSaved() {
@@ -2888,6 +2916,9 @@
onPictureInPictureModeChangedProvider.removeOnPictureInPictureModeChangedListener(
mOnPictureInPictureModeChangedListener);
}
+ if (mHost instanceof MenuHost) {
+ ((MenuHost) mHost).removeMenuProvider(mMenuProvider);
+ }
mHost = null;
mContainer = null;
mParent = null;