Floating toolbar for text selection on Android Marshmallow: parsing innovations

In Andriod, when you select text, a menu appears with actions that you can perform: “Cut”, “Copy”, “Send”. Android Marshmallow (SDK 23) has the opportunity to expand this menu and give the user easy access to additional features when working with text: “Translate”, “Comment”, “Quote”.
In the process of preparing for my presentation at the GDG conference in Nizhny Novgorod, I discovered that this new opportunity is extremely poorly documented, the only available article is not entirely true, and there are vanishingly few examples of using this opportunity on the web. I had to figure it out myself. The results of the study and I want to share. This can save you a lot of time.
Since various menus in Android have appeared evolutionarily, the easiest way to start a story is “from the stove”. Developers with experience can safely flip directly to the "New" section without risking missing something. If anything - then come back. Younger developers can find practical examples under spoilers.
Old
The first one. Menu vulgaris
The usual menu, which at times it was called up by pressing the hardware key "Menu", is described in the application resources as an XML file.
Menu items will be sorted by android: orderInCategory field.
A menu is created by calling the inflater in the onCreateOptionsMenu () method .
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.my_menu, menu);
return true;
}
Information about choosing a menu item falls into the onOptionsItemSelected () method .
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_foo:
toast("foo");
return true;
case R.id.action_foobar:
toast("bar");
return true;
case R.id.action_baz:
toast("baz");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Everything described above leads to the appearance of such a menu:

Everything is simple and familiar.
The second one. Context menu
Context menus are most often used when working with lists. But this story would be more about lists than about menus, so let's leave it outside the scope of the article. Examples will be without them.
Consider a simple example.
You can register an element, for example, in onCreate () :
@Override
protected void onCreate(Bundle savedInstanceState) {
...
registerForContextMenu(findViewById(R.id.text_view_one));
}
Further, everything is similar to the previous paragraph, only inside other methods ( onCreateContextMenu () and onContextItemSelected () ):
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.my_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_foo:
toast("foo");
return true;
case R.id.action_foobar:
toast("bar");
return true;
case R.id.action_baz:
toast("baz");
return true;
default:
return super.onContextItemSelected(item);
}
}
With a long press on an element with text, a context menu appears.

The third. Menus in AppBar and Toolbar
Fields for the description of the icons and the location were supplemented by XML with a description of the menu. For the rest, this remains the same menu with the onCreateOptionsMenu () and onOptionsItemSelected () known to us .
![]() Visible portion of the menu | ![]() The hidden part of the menu, if you click on the "three points" |
At this level, everyone usually knows about the menu after the first new book. Thank you for the patience to read here, it will be more interesting further.
Fourth. Contextual action mode
In order to consider the operation of this mode, we need an element that can be "selected". Take for example ToggleButton . The application activates this mode by itself, accordingly, we will do this when the ToggleButton status changes:
checkedListener =
new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked)
{
if (isChecked) {
actionMode = startActionMode(actionModeCallback);
actionMode.setTitle("Action Mode");
} else {
if (actionMode != null) {
actionMode.finish();
}
}
}
};
toggleButton.setOnCheckedChangeListener(checkedListener);
actionModeCallback is an instance of the ActionMode.Callback class that contains callbacks for working with menus. The menu remained the same, all with the same good old mechanics:
actionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.my_menu_two, menu);
return true;
}
...
@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
toggleButton.setChecked(false);
}
};
Pay attention to the value that we save in the variable actionMode. This is an instance of the ActionMode class , we need it to be able to change the title ( actionMode.setTitle () ), the subtitle ( actionMode.setSubtitle () ), and also end this mode ( actionMode.finish () ). After the action is completed, the mode does not automatically end, and if we need to, then we must complete it ourselves.
The user's choice is also processed in the usual way:
actionModeCallback = new ActionMode.Callback() {
...
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_compass:
toast("compass");
return true;
case R.id.action_camera:
toast("camera");
return true;
default:
return false;
}
}
};
The menu for contextual actions, obviously, should be different from the main one, so let's create another XML with a description.
We get this behavior:
![]() | ![]() |
Everything old, that is, known at least with SDK 11 is now over, the unknown has begun.
New
In Android Marshmallow (aka 6.0, aka SDK 23), two new features appeared in the menu. Both of these innovations are not yet supported by the Support Library and work only on devices with SDK 23. Therefore, before calling the methods that appear, you need to check the SDK number of the device on which the application is running.
For the convenience of the story, we will get rid of these checks by specifying minSdkVersion 23.
Fifth. Another context menu
The method for creating a “Contextual Action Mode” (described above) has been expanded. The second parameter startActionMode () can pass a constant that sets the type of display.
The value of ActionMode.TYPE_PRIMARY corresponds to the old behavior. That is, startActionMode (actionModeCallback) and startActionMode (actionModeCallback, ActionMode.TYPE_PRIMARY) are the same thing.
If you set the type ActionMode.TYPE_FLOATING , then the menu takes the following form:

No icons. The location is horizontal.
If there are a lot of points so that they do not fit in width, then the familiar “three points” will appear:
![]() | ![]() |
Everything else is exactly the same as in “Contextual action mode” (see above).
Sixth. Menu extension for text selection
And finally, what was promised in the article about innovations in Android Marshmallow . Now you can supplement the menu that appears when you select text with your own items. An animated picture of this miracle was at the very beginning of the article, but if you want, it is again under the spoiler.

Implementation
To achieve this effect, you first need to create a callback inherited from ActionMode.Callback. That is, completely similar to what we created two sections above for “Contextual action mode” .
Further, for all the elements in which we want to expand the menu when editing, we need to specify this callback:
textView.setTextIsSelectable(true);
textView.setCustomSelectionActionModeCallback(actionModeCallback);
editText.setCustomSelectionActionModeCallback(actionModeCallback);
editText.setCustomInsertionActionModeCallback(actionModeCallback);
The callback specified in setCustomSelectionActionModeCallback () will be used if there is selected text. Specified in setCustomInsertionActionModeCallback () - if there is no text. They were divided because not all actions make sense when nothing is selected, and, accordingly, the contents of the menus that appear should be different.
![]() | ![]() |
On an empty EditText, it looks like this:
![]() | ![]() |
Oh. Where did my menu go?
Check out the screenshots below. Have an idea where all the added menu items have gone?
![]() | ![]() |
The hint can be found here: I will

explain. The probability that the remaining items will not fit in the second, vertical part of the menu is also quite high. And there will be a scroll. In the screenshots above it happened.
The problem is that the user does not see any signs of scroll: the scroll icon disappears almost immediately, and the edge of the non-positioned item does not look out. Started a bug, let's see what the creators of this feature say. code.google.com/p/android/issues/detail?id=195043
Where did my menu go again?

This time another problem: the menu with fullscreen input mode as described above does not expand. I found only one workaround: turn off fullscreen mode with android: imeOptions = "flagNoExtractUi".
How to place your item in front of the standard?
Native menu items have order parameters from 1 to 5. Therefore, using android: orderInCategory in the menu description, you cannot set the position in front of the native items. But you can change the order of the items in the already formed menu, for example, like this:
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// родные пункты меню "нумеруются" с 1,
// дополнительные я "пронумеровал" со 100
while (menu.getItem(0).getOrder() < 100) {
MenuItem item = menu.getItem(0);
menu.removeItem(item.getItemId());
// теперь родные будут "нумероваться" с 200, то есть
// станут после дополнительных
menu.add(item.getGroupId(), item.getItemId(),
item.getOrder() + 200, item.getTitle());
}
return true;
}
We get the result:
![]() | ![]() |
Oddities in the documentation
There is no normal documentation for expanding the menu when selecting text. There is an article already mentioned article about the latest in Android Marshmallow .
I re-read this place several dozen times, but I could not correlate what was written there with practice. I tell in order.

If you read this article or tried it yourself, you noticed that startActionMode (callback, ActionMode.TYPE_FLOATING) does create a floating toolbar, but not at all for selection. And for selection it is created by setCustomSelectionActionModeCallback () .

The second incomprehensible instruction. The setCustomSelectionActionModeCallback () method of the android.widget.TextView class expects to receiveActionMode.Callback .

android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.java
If you look in android.widget.Editor , then the mentioned mCustomSelectionActionModeCallback field also has the type ActionMode.Callback :

And nowhere else This code is not expected to have a custom callback of type ActionMode.Callback2 .
android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/Editor.java The
article continues to persistently talk about how to use ActionMode.Callback2 :

My assumption is that the article is overlooked by the editor got a fragment of some internal documentation onActionMode.Callback2 . Do you have other hypotheses, write about it.
Example
Entire code from this article can be found on GitHub .













