Input compatibility for Chromebooks

Users on laptops often use different input devices than on phones.

Running on Chromebooks, Android apps find themselves in an
environment they were not designed for, displayed inside a window
on a laptop with mouse, touchpad and keyboard.
While Android does have support for those devices, they are rarely properly
implemented in apps. In order to make these apps work, Chromebooks use
a compatibility mode that is applied to all apps by default.

Compatibility mode

In compatibility mode, your app will receive events differently from how they
are provided by Android APIs. This mode is useful for developers that do not
want to implement Chromebook-specific features, but want their
touch-centric app to just work.

This tells Android that you are targeting PC devices that are
typically used with keyboard, mouse, and touchpad. It disables the
compatibility mode and allows you to develop custom behavior for mouse and
touchpad.

Beware the DecorCaptionView

In free-form window mode, the apps caption bar is part of your view
hierarchy and under your control. You generally do not have to be aware of
this, but there are cases where you have to be careful:

Do not fiddle with Window.getDecorView(). If you want to add top-level
views, add them to the view you have set as Activity.setContentView().

Do not expect your Activity.setContentView() to be at (0, 0) of your
app. That’s where the caption bar is.

If possible, avoid using MotionEvent.getRawX() or MotionEvent.getRawY().
If you do use them, use them in conjunction with
View.getLocationOnScreen() to transform coordinates to view-space
coordinates.

Input device support

Once you are targeting PC devices via the android.hardware.type.pc flag,
you can expect the following events:

Mouse and touchpad support

Mouse and touchpad both generate MotionEvents just like touch events.
Check MotionEvent.getSource() to distinguish
between SOURCE_MOUSE and SOURCE_TOUCHSCREEN.

Mouse/touchpad movement. Generates ACTION_HOVER_MOVE events. Those are
handled in View.onGenericMotionEvent().

Mouse/touchpad buttons. Sends ACTION_BUTTON_PRESS and
ACTION_BUTTON_RELEASE events to View.onGenericMotionEvent(). You can
also check for pressed buttons in all mouse/touchpad events by using
getButtonState().

Touchpad scrolling. Scrolling using two fingers on the touchpad is
reported similar to touchscreen drag events to allow for smooth and kinetic
scrolling. Use getSource() if you want touchscreen drag and touchpad
scrolling to behave differently.

Mouse/touchpad click and drag. Click and drag events are very similar
to touchscreen drag events. Use getButtonState() to distinguish click
and drag from a touchpad scroll event. Click and drag will always be
accompanied by a button, while touchpad scrolling does not.

This method was added in API level 23 so if you want to allow the same
functionality on other devices that could be running an older version of Android,
you can use generic motion events as shown above.

Keyboard

Keyboard input is important on desktop and laptop devices. It is also highly
required for accessibility.

To get proper input focus activation, you should follow the normal resource
additions as outlined in the default Android API:

If you want to handle keyboard input yourself, you can use the default
functions via KeyEvent.callback. There is no need to handle keyboard input
inside a TextEdit element.

If you want to edit text yourself, you should use onKeyDown,
onKeyLongPress, onKeyUp but not onKeyPreIME events unless you want
to implement the full breadth of IME—which is not recommended.

Stylus

A stylus reports events similar to a touchscreen via View.onTouchEvent().
However, stylus events carry more information that should be accounted for:

MotionEvent.getToolType(). The tool type allows you to distinguish
TOOL_TYPE_FINGER from TOOL_TYPE_STYLUS events. It also allows you
to recognize the usage of the eraser side of a pen if the user has one
via TOOL_TYPE_ERASER.

MotionEvent.getPressure(). Represents the physical pressure applied to
the stylus pen if supported.

MotionEvent.AXIS_TILT/AXIS_ORIENTATION. Can be used with
MotionEvent.getAxisValue() to read the physical tilt and orientation of
the stylus if supported.

Historical points

Android batches input events to be delivered once per frame. A stylus pen,
however, can report at much higher frequencies (200Hz being quite common).
When creating drawing apps, it is important to use the points acquired
from the historical API:

MotionEvent.getHistoricalX()

MotionEvent.getHistoricalY()

MotionEvent.getHistoricalPressure()

MotionEvent.getHistoricalAxisValue()

Palm rejection

Chrome OS attempts to recognize resting palms so they are never
reported to the app. However this is not always possible: sometimes a touch may
be reported before the OS recognizes it as a palm. In that case, touches will
be cancelled by reporting an ACTION_CANCEL event.

This event tells the app that all touches are invalid and it should undo
all interactions caused by the touches.
For example, in case of drawing apps, the app will temporarily draw new
lines and commit them to the canvas only once the touch series is finished
cleanly. If the touch is cancelled, that temporary line can be easily removed.

Note: An easy option for great UX is to provide a setting for users to
disable drawing via touch, and just use the active stylus for drawing.

Note-taking intent

Chrome OS displays a list of apps that are registered to handle intents
containing an org.chromium.arc.intent.action.CREATE_NOTE action and the
Intent.CATEGORY_DEFAULT (i.e. android.intent.category.DEFAULT) category
as shown in the following code snippet:

The user will be able to select an app, and that app will later be launched
when a note-taking app is requested.

When the user requests creating a new note, the app will be launched using
an intent containing just the aforementioned action and category. The app
should create an empty note in a mode where the user can write using a stylus.

When the user requests annotating an image (e.g. a screenshot or downloaded
image) context, the app launches using an intent with the
aforementioned action and category, including ClipData containing one or more items
with content:// URIs. The app should create a note that uses the first attached
image as a background image in a mode where the user can draw on it using a
stylus.

You will now see the stylus entry points:
- In the shelf, you can tap the stylus button and choose “New note”. This
should open a blank drawing note in your application.
- If you take a screenshot (from shelf: stylus button > Capture screen) or
download an image, you should see the option “Annotate image” in the
notification. This should open your app with the image ready to be
annotated.

Gamepads

Chromebooks support up to 4 gamepads and follows standard
Android APIs for reporting them. Unfortunately it is quite common that
gamepads not designed for Android show the right button mapping.