From b57c8180da62bbce867bc221585cb042fe81d4de Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Thu, 2 Feb 2023 14:19:37 +0900 Subject: [PATCH] Set smart selection menu into context menu item This CL extract the common logic between ActionMode and ContextMenu and put it to AssistantCallbackHelper. Bug: 267984410 Test: atest TextViewContextMenuTest SelectionActionModeHelperTest Change-Id: I82a1d4873cd34a250c920e91a88382b509a46adc --- core/java/android/widget/Editor.java | 323 +++++++++++------- .../widget/SelectionActionModeHelper.java | 2 +- core/tests/coretests/AndroidManifest.xml | 9 + .../res/layout/textview_contextmenu.xml | 32 ++ .../widget/TextViewContextMenuActivity.java | 30 ++ .../widget/TextViewContextMenuTest.java | 170 +++++++++ 6 files changed, 437 insertions(+), 129 deletions(-) create mode 100644 core/tests/coretests/res/layout/textview_contextmenu.xml create mode 100644 core/tests/coretests/src/android/widget/TextViewContextMenuActivity.java create mode 100644 core/tests/coretests/src/android/widget/TextViewContextMenuTest.java diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 159775c4e0c0a..aa4cf7ee22a78 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -3090,7 +3090,33 @@ public class Editor { mContextMenuAnchorY = y; } - void onCreateContextMenu(ContextMenu menu) { + private void setAssistContextMenuItems(Menu menu) { + final TextClassification textClassification = + getSelectionActionModeHelper().getTextClassification(); + if (textClassification == null) { + return; + } + + final AssistantCallbackHelper helper = + new AssistantCallbackHelper(getSelectionActionModeHelper()); + helper.updateAssistMenuItems(menu, (MenuItem item) -> { + getSelectionActionModeHelper() + .onSelectionAction(item.getItemId(), item.getTitle().toString()); + + if (mProcessTextIntentActionsHandler.performMenuItemAction(item)) { + return true; + } + if (item.getGroupId() == TextView.ID_ASSIST && helper.onAssistMenuItemClicked(item)) { + return true; + } + return mTextView.onTextContextMenuItem(item.getItemId()); + }); + } + + /** + * Called when the context menu is created. + */ + public void onCreateContextMenu(ContextMenu menu) { if (mIsBeingLongClicked || Float.isNaN(mContextMenuAnchorX) || Float.isNaN(mContextMenuAnchorY)) { return; @@ -3153,6 +3179,8 @@ public class Editor { menu.setOptionalIconsVisible(true); menu.setGroupDividerEnabled(true); + setAssistContextMenuItems(menu); + final int keyboard = mTextView.getResources().getConfiguration().keyboard; menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY); } else { @@ -4308,6 +4336,165 @@ public class Editor { } } + /** + * Helper class for UI component (e.g. ActionMode and ContextMenu) with TextClassification. + * @hide + */ + @VisibleForTesting + public class AssistantCallbackHelper { + private final Map mAssistClickHandlers = new HashMap<>(); + @Nullable private TextClassification mPrevTextClassification; + @NonNull private final SelectionActionModeHelper mHelper; + + public AssistantCallbackHelper(SelectionActionModeHelper helper) { + mHelper = helper; + } + + /** + * Clears callback handlers. + */ + public void clearCallbackHandlers() { + mAssistClickHandlers.clear(); + } + + /** + * Get on click listener assisiated with the MenuItem. + */ + public OnClickListener getOnClickListener(MenuItem key) { + return mAssistClickHandlers.get(key); + } + + /** + * Update menu items. + * + * Existing assist menu will be cleared and latest assist menu will be added. + */ + public void updateAssistMenuItems(Menu menu, MenuItem.OnMenuItemClickListener listener) { + final TextClassification textClassification = mHelper.getTextClassification(); + if (mPrevTextClassification == textClassification) { + // Already handled. + return; + } + clearAssistMenuItems(menu); + if (textClassification == null) { + return; + } + if (!shouldEnableAssistMenuItems()) { + return; + } + if (!textClassification.getActions().isEmpty()) { + // Primary assist action (Always shown). + final MenuItem item = addAssistMenuItem(menu, + textClassification.getActions().get(0), TextView.ID_ASSIST, + ACTION_MODE_MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS, + listener); + item.setIntent(textClassification.getIntent()); + } else if (hasLegacyAssistItem(textClassification)) { + // Legacy primary assist action (Always shown). + final MenuItem item = menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, + ACTION_MODE_MENU_ITEM_ORDER_ASSIST, + textClassification.getLabel()) + .setIcon(textClassification.getIcon()) + .setIntent(textClassification.getIntent()); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + mAssistClickHandlers.put(item, TextClassification.createIntentOnClickListener( + TextClassification.createPendingIntent(mTextView.getContext(), + textClassification.getIntent(), + createAssistMenuItemPendingIntentRequestCode()))); + } + final int count = textClassification.getActions().size(); + for (int i = 1; i < count; i++) { + // Secondary assist action (Never shown). + addAssistMenuItem(menu, textClassification.getActions().get(i), Menu.NONE, + ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1, + MenuItem.SHOW_AS_ACTION_NEVER, listener); + } + mPrevTextClassification = textClassification; + } + + private MenuItem addAssistMenuItem(Menu menu, RemoteAction action, int itemId, int order, + int showAsAction, MenuItem.OnMenuItemClickListener listener) { + final MenuItem item = menu.add(TextView.ID_ASSIST, itemId, order, action.getTitle()) + .setContentDescription(action.getContentDescription()); + if (action.shouldShowIcon()) { + item.setIcon(action.getIcon().loadDrawable(mTextView.getContext())); + } + item.setShowAsAction(showAsAction); + mAssistClickHandlers.put(item, + TextClassification.createIntentOnClickListener(action.getActionIntent())); + mA11ySmartActions.addAction(action); + if (listener != null) { + item.setOnMenuItemClickListener(listener); + } + return item; + } + + private void clearAssistMenuItems(Menu menu) { + int i = 0; + while (i < menu.size()) { + final MenuItem menuItem = menu.getItem(i); + if (menuItem.getGroupId() == TextView.ID_ASSIST) { + menu.removeItem(menuItem.getItemId()); + continue; + } + i++; + } + mA11ySmartActions.reset(); + } + + private boolean hasLegacyAssistItem(TextClassification classification) { + // Check whether we have the UI data and action. + return (classification.getIcon() != null || !TextUtils.isEmpty( + classification.getLabel())) && (classification.getIntent() != null + || classification.getOnClickListener() != null); + } + + private boolean shouldEnableAssistMenuItems() { + return mTextView.isDeviceProvisioned() + && TextClassificationManager.getSettings(mTextView.getContext()) + .isSmartTextShareEnabled(); + } + + private int createAssistMenuItemPendingIntentRequestCode() { + return mTextView.hasSelection() + ? mTextView.getText().subSequence( + mTextView.getSelectionStart(), mTextView.getSelectionEnd()) + .hashCode() + : 0; + } + + /** + * Called when the assist menu on ActionMode or ContextMenu is called. + */ + public boolean onAssistMenuItemClicked(MenuItem assistMenuItem) { + Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST); + + final TextClassification textClassification = + getSelectionActionModeHelper().getTextClassification(); + if (!shouldEnableAssistMenuItems() || textClassification == null) { + // No textClassification result to handle the click. Eat the click. + return true; + } + + OnClickListener onClickListener = getOnClickListener(assistMenuItem); + if (onClickListener == null) { + final Intent intent = assistMenuItem.getIntent(); + if (intent != null) { + onClickListener = TextClassification.createIntentOnClickListener( + TextClassification.createPendingIntent( + mTextView.getContext(), intent, + createAssistMenuItemPendingIntentRequestCode())); + } + } + if (onClickListener != null) { + onClickListener.onClick(mTextView); + stopTextActionMode(); + } + // We tried our best. + return true; + } + } + /** * An ActionMode Callback class that is used to provide actions while in text insertion or * selection mode. @@ -4320,9 +4507,8 @@ public class Editor { private final RectF mSelectionBounds = new RectF(); private final boolean mHasSelection; private final int mHandleHeight; - private final Map mAssistClickHandlers = new HashMap<>(); - @Nullable - private TextClassification mPrevTextClassification; + private final AssistantCallbackHelper mHelper = new AssistantCallbackHelper( + getSelectionActionModeHelper()); TextActionModeCallback(@TextActionMode int mode) { mHasSelection = mode == TextActionMode.SELECTION @@ -4351,7 +4537,7 @@ public class Editor { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mAssistClickHandlers.clear(); + mHelper.clearCallbackHandlers(); mode.setTitle(null); mode.setSubtitle(null); @@ -4432,14 +4618,14 @@ public class Editor { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItems(menu); + mHelper.updateAssistMenuItems(menu, null); } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { updateSelectAllItem(menu); updateReplaceItem(menu); - updateAssistMenuItems(menu); + mHelper.updateAssistMenuItems(menu, null); Callback customCallback = getCustomCallback(); if (customCallback != null) { @@ -4472,125 +4658,6 @@ public class Editor { } } - private void updateAssistMenuItems(Menu menu) { - final TextClassification textClassification = - getSelectionActionModeHelper().getTextClassification(); - if (mPrevTextClassification == textClassification) { - // Already handled. - return; - } - clearAssistMenuItems(menu); - if (textClassification == null) { - return; - } - if (!shouldEnableAssistMenuItems()) { - return; - } - if (!textClassification.getActions().isEmpty()) { - // Primary assist action (Always shown). - final MenuItem item = addAssistMenuItem(menu, - textClassification.getActions().get(0), TextView.ID_ASSIST, - ACTION_MODE_MENU_ITEM_ORDER_ASSIST, MenuItem.SHOW_AS_ACTION_ALWAYS); - item.setIntent(textClassification.getIntent()); - } else if (hasLegacyAssistItem(textClassification)) { - // Legacy primary assist action (Always shown). - final MenuItem item = menu.add( - TextView.ID_ASSIST, TextView.ID_ASSIST, ACTION_MODE_MENU_ITEM_ORDER_ASSIST, - textClassification.getLabel()) - .setIcon(textClassification.getIcon()) - .setIntent(textClassification.getIntent()); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - mAssistClickHandlers.put(item, TextClassification.createIntentOnClickListener( - TextClassification.createPendingIntent(mTextView.getContext(), - textClassification.getIntent(), - createAssistMenuItemPendingIntentRequestCode()))); - } - final int count = textClassification.getActions().size(); - for (int i = 1; i < count; i++) { - // Secondary assist action (Never shown). - addAssistMenuItem(menu, textClassification.getActions().get(i), Menu.NONE, - ACTION_MODE_MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i - 1, - MenuItem.SHOW_AS_ACTION_NEVER); - } - mPrevTextClassification = textClassification; - } - - private MenuItem addAssistMenuItem(Menu menu, RemoteAction action, int itemId, int order, - int showAsAction) { - final MenuItem item = menu.add(TextView.ID_ASSIST, itemId, order, action.getTitle()) - .setContentDescription(action.getContentDescription()); - if (action.shouldShowIcon()) { - item.setIcon(action.getIcon().loadDrawable(mTextView.getContext())); - } - item.setShowAsAction(showAsAction); - mAssistClickHandlers.put(item, - TextClassification.createIntentOnClickListener(action.getActionIntent())); - mA11ySmartActions.addAction(action); - return item; - } - - private void clearAssistMenuItems(Menu menu) { - int i = 0; - while (i < menu.size()) { - final MenuItem menuItem = menu.getItem(i); - if (menuItem.getGroupId() == TextView.ID_ASSIST) { - menu.removeItem(menuItem.getItemId()); - continue; - } - i++; - } - mA11ySmartActions.reset(); - } - - private boolean hasLegacyAssistItem(TextClassification classification) { - // Check whether we have the UI data and and action. - return (classification.getIcon() != null || !TextUtils.isEmpty( - classification.getLabel())) && (classification.getIntent() != null - || classification.getOnClickListener() != null); - } - - private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) { - Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST); - - final TextClassification textClassification = - getSelectionActionModeHelper().getTextClassification(); - if (!shouldEnableAssistMenuItems() || textClassification == null) { - // No textClassification result to handle the click. Eat the click. - return true; - } - - OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem); - if (onClickListener == null) { - final Intent intent = assistMenuItem.getIntent(); - if (intent != null) { - onClickListener = TextClassification.createIntentOnClickListener( - TextClassification.createPendingIntent( - mTextView.getContext(), intent, - createAssistMenuItemPendingIntentRequestCode())); - } - } - if (onClickListener != null) { - onClickListener.onClick(mTextView); - stopTextActionMode(); - } - // We tried our best. - return true; - } - - private int createAssistMenuItemPendingIntentRequestCode() { - return mTextView.hasSelection() - ? mTextView.getText().subSequence( - mTextView.getSelectionStart(), mTextView.getSelectionEnd()) - .hashCode() - : 0; - } - - private boolean shouldEnableAssistMenuItems() { - return mTextView.isDeviceProvisioned() - && TextClassificationManager.getSettings(mTextView.getContext()) - .isSmartTextShareEnabled(); - } - @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { getSelectionActionModeHelper() @@ -4603,7 +4670,7 @@ public class Editor { if (customCallback != null && customCallback.onActionItemClicked(mode, item)) { return true; } - if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) { + if (item.getGroupId() == TextView.ID_ASSIST && mHelper.onAssistMenuItemClicked(item)) { return true; } return mTextView.onTextContextMenuItem(item.getItemId()); @@ -4633,7 +4700,7 @@ public class Editor { mSelectionModifierCursorController.hide(); } - mAssistClickHandlers.clear(); + mHelper.clearCallbackHandlers(); mRequestingLinkActionMode = false; } diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index c1800cffa7924..06cacf3b0e3e5 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -70,7 +70,7 @@ import java.util.regex.Pattern; */ @UiThread @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class SelectionActionModeHelper { +public class SelectionActionModeHelper { private static final String LOG_TAG = "SelectActionModeHelper"; diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f6216fa659152..6bbab0efab3b2 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1607,6 +1607,15 @@ + + + + + + + diff --git a/core/tests/coretests/res/layout/textview_contextmenu.xml b/core/tests/coretests/res/layout/textview_contextmenu.xml new file mode 100644 index 0000000000000..50d351c09c51b --- /dev/null +++ b/core/tests/coretests/res/layout/textview_contextmenu.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuActivity.java b/core/tests/coretests/src/android/widget/TextViewContextMenuActivity.java new file mode 100644 index 0000000000000..616f29b7462ba --- /dev/null +++ b/core/tests/coretests/src/android/widget/TextViewContextMenuActivity.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.app.Activity; +import android.os.Bundle; + +import com.android.frameworks.coretests.R; + +public class TextViewContextMenuActivity extends Activity { + @Override + public void onCreate(Bundle savedBundleInstance) { + super.onCreate(savedBundleInstance); + setContentView(R.layout.textview_contextmenu); + } +} diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java new file mode 100644 index 0000000000000..0c7550e79cfa5 --- /dev/null +++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyChar; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.view.textclassifier.TextClassification; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.filters.MediumTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +/** + * TextViewTest tests {@link TextView}. + */ +@RunWith(AndroidJUnit4.class) +@MediumTest +public class TextViewContextMenuTest { + private static final String INTENT_ACTION_MOCK_ACTION_TEXT_CLASSIFICATION = + "android.text.coretest.textclassifiation"; + private static final String ACTION_TITLE = "ACTION_TITLE"; + private static final String ACTION_DESCRIPTION = "ACTION_DESCRIPTION"; + + @Rule + public final ActivityTestRule mActivityRule = + new ActivityTestRule<>(TextViewContextMenuActivity.class); + + // Setup MenuItem mock with chaining. + private MenuItem newMockMenuItem() { + MenuItem mockItem = mock(MenuItem.class); + when(mockItem.setAlphabeticShortcut(anyChar())).thenReturn(mockItem); + when(mockItem.setAlphabeticShortcut(anyChar(), anyInt())).thenReturn(mockItem); + when(mockItem.setOnMenuItemClickListener(any())).thenReturn(mockItem); + when(mockItem.setEnabled(anyBoolean())).thenReturn(mockItem); + when(mockItem.setIcon(any())).thenReturn(mockItem); + when(mockItem.setIntent(any())).thenReturn(mockItem); + when(mockItem.setContentDescription(any())).thenReturn(mockItem); + return mockItem; + } + + private RemoteAction createRemoteAction() { + Intent intent = new Intent(INTENT_ACTION_MOCK_ACTION_TEXT_CLASSIFICATION) + .setPackage(mActivity.getPackageName()); + PendingIntent pIntent = PendingIntent.getBroadcast(mActivity, 0, intent, + PendingIntent.FLAG_MUTABLE); + return new RemoteAction( + Icon.createWithResource(mActivity, android.R.drawable.btn_star), + ACTION_TITLE, ACTION_DESCRIPTION, pIntent); + } + + private Activity mActivity; + private SelectionActionModeHelper mMockHelper; + private Editor.AssistantCallbackHelper mCallbackHelper; + + @Before + public void setUp() { + mActivity = mActivityRule.getActivity(); + EditText et = mActivity.findViewById(R.id.editText); + + mMockHelper = mock(SelectionActionModeHelper.class); + mCallbackHelper = et.getEditorForTesting().new AssistantCallbackHelper(mMockHelper); + } + + @UiThreadTest + @Test + public void testNoMenuInteraction_noTextClassification() { + when(mMockHelper.getTextClassification()).thenReturn(null); + ContextMenu menu = mock(ContextMenu.class); + mCallbackHelper.updateAssistMenuItems(menu, null); + verifyNoMoreInteractions(menu); + } + + @UiThreadTest + @Test + public void testAddMenuForTextClassification() { + // Setup + RemoteAction action = createRemoteAction(); + TextClassification classification = new TextClassification.Builder() + .addAction(action).build(); + when(mMockHelper.getTextClassification()).thenReturn(classification); + + ContextMenu menu = mock(ContextMenu.class); + MenuItem mockMenuItem = newMockMenuItem(); + when(menu.add(anyInt(), anyInt(), anyInt(), any())).thenReturn(mockMenuItem); + + // Execute + mCallbackHelper.updateAssistMenuItems(menu, null); + + // Verify + ArgumentCaptor idCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor titleCaptor = ArgumentCaptor.forClass(CharSequence.class); + + verify(menu, times(1)).add(anyInt(), idCaptor.capture(), anyInt(), titleCaptor.capture()); + + assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST); + assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE); + verify(mockMenuItem, times(1)).setContentDescription(eq(ACTION_DESCRIPTION)); + } + + @UiThreadTest + @Test + public void testAddMenuForLegacyTextClassification() { + // Setup + Intent intent = new Intent(INTENT_ACTION_MOCK_ACTION_TEXT_CLASSIFICATION) + .setPackage(mActivity.getPackageName()); + TextClassification classification = new TextClassification.Builder() + .setIcon(mActivity.getResources().getDrawable(android.R.drawable.star_on)) + .setLabel(ACTION_TITLE) + .setIntent(intent) + .build(); + when(mMockHelper.getTextClassification()).thenReturn(classification); + + ContextMenu menu = mock(ContextMenu.class); + MenuItem mockMenuItem = newMockMenuItem(); + when(menu.add(anyInt(), anyInt(), anyInt(), any())).thenReturn(mockMenuItem); + + // Execute + mCallbackHelper.updateAssistMenuItems(menu, null); + + // Verify + ArgumentCaptor idCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor titleCaptor = ArgumentCaptor.forClass(CharSequence.class); + + verify(menu, times(1)).add(anyInt(), idCaptor.capture(), anyInt(), titleCaptor.capture()); + + assertThat(idCaptor.getValue()).isEqualTo(TextView.ID_ASSIST); + assertThat(titleCaptor.getValue().toString()).isEqualTo(ACTION_TITLE); + } +}