Ready, Set, Type! Developer’s Guide to the PRIV Keyboard (Part 1)

Code Samples

priv

In this 2-part blog series, we tell you everything you need to know to take full advantage of the keyboards available on the Priv™ by BlackBerry®. Part 1 will cover topics pertaining to a device with both a physical and virtual keyboard. Part 2 will cover how to use the capacitive touch keyboard.

PRIV offers multiple methods to interact with the device.  For typing, you have the option to use either the touch screen keyboard or the slide-out, physical keyboard.  The physical keyboard is also touch sensitive, allowing you to use it for scrolling and gestures.  These features have been designed to integrate seamlessly with existing applications, meaning applications shouldn’t require any modifications to take advantage of these features.  However, knowing the state of the physical keyboard can be important for Input Method Editors (IME), such as custom Android™ virtual keyboards.  If you do wish to make direct use of these features in your application – such as taking action when the keyboard is slid open or shut – you can make use of existing Android APIs.

Detecting if the Physical Keyboard is Open or Closed

The keyboard state can be derived by examining the Configuration of your activity or service – including an input method service – similar to how you would detect an external keyboard.  Configuration.keyboard shows the type of keyboard attached.  Configuration.keyboardHidden indicates whether any keyboard (soft or hard) is available.  In the case of a soft keyboard, it should be available even if not currently shown and should always return KEYBOARDHIDDEN_NO on PRIV.

Configuration,hardKeyboardHidden indicates whether a physical keyboard is available. This is defined to allow for 3 possible values, but on PRIV it has either the value HARDKEYBOARDHIDDEN_NO, indicating that the slider is open or that an external keyboard is connected, or HARDKEYBOARDHIDDEN_YES, indicating that the slider is closed. Note that if an external keyboard is connected to a slider device, this field will not tell you if the slider is open or closed because the field will have a value of HARDKEYBOARDHIDDEN_NO.  The following charts summarizes these values on PRIV.

 Values for: Physical Keyboard Open Physical Keyboard Closed
Configuration.keyboard KEYBOARD_QWERTY KEYBOARD_NOKEYS
Configuration.keyboardHidden KEYBOARDHIDDEN_NO KEYBOARDHIDDEN_NO
Configuration.hardKeyboardHidden HARDKEYBOARDHIDDEN_NO HARDKEYBOARDHIDDEN_YES

Below is some sample code that reads these configuration values.  A complete working sample application that reports the keyboard status can be found here:  SliderDemo

Configuration conf = getResources().getConfiguration();

       
String keyboardValue;
switch(conf.keyboard) {
    case Configuration.KEYBOARD_NOKEYS: keyboardValue = "KEYBOARD_NOKEYS"; break;
    case Configuration.KEYBOARD_12KEY: keyboardValue = "KEYBOARD_12KEY"; break;
    case Configuration.KEYBOARD_QWERTY: keyboardValue = "KEYBOARD_QWERTY"; break;
    default: keyboardValue = "Unknown";
}

String hardKeyboardHiddenValue = (conf.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) 
        ? "HARDKEYBOARDHIDDEN_NO" : "HARDKEYBOARDHIDDEN_YES";

Physical Keyboard vs External Keyboard

You may have noticed that the configuration values used here are the same that are used to detect when an external Bluetooth® or USB keyboard is attached to an Android device.  There are some similarities and differences to how the device behaves when the keyboard is slid open or closed compared to when an external Bluetooth or USB keyboard is attached.

What’s the Same

When an external Bluetooth or USB keyboard is connected to PRIV, the configuration values are the same as the first column in the chart above, regardless of the position of the sliding keyboard.  This allows support for external keyboards no matter the position of the sliding keyboard.

What’s Different

Usually when “keyboardHidden|keyboard” is added to the attribute android:configChanges and an external Bluetooth or USB keyboard is connected to an Android device, the current activity is restarted and configuration is updated.  Since connecting an external keyboard is infrequent, users aren’t that impacted.  However, users may frequently open and close the physical keyboard.  If the activity is restarted each time the keyboard is opened or closed, it would have a negative impact on both system resources and the user experience.  Imagine a user losing text they’ve entered or their scroll position every time they opened or closed the keyboard.  For this reason, we don’t restart the activity in this circumstance and instead only update the configuration, which triggers the onConfigurationChanged method.  Note that you must add “keyboardHidden|keyboard” for the attribute android:configChanges in your application’s manifest file in order for onCofigurationChanged() to be fired for these keyboard events.  If “keyboardHidden|keyboard” is not added, neither onRestart() nor onConfigurationChanged() are fired for these keyboard events. The Configuration retrieved through getResources().getConfiguration() will be correct regardless of whether or not “keyboardHidden|keyboard” is used.  This is summarized in the table below.

With
keyboardHidden|keyboard

Without
keyboardHidden|keyboard

onCofigurationChanged Fired?

onRestart Fired?

onCofigurationChanged Fired?

onRestart Fired?

Priv Keyboard Slid Open or Closed

YES

NO

NO

NO

External Keyboard Connected or Disconnected

YES

YES

NO

NO

Building a Physical Keyboard-Friendly Application

If your app uses text input, you should aim for the best possible text input experience for users, whether they’re using a physical keyboard or a soft, touch screen keyboard. Part of providing a positive user experience includes being consistent with user expectations of text input using the same input method. A few key principles will help you (and the IME developer) provide a consistent experience.

Help the IME Know When Text is Actually Being Edited

Most fields derived from TextView/EditText implement the recommendations below.  But if you’re implementing a custom text field that isn’t a descendent of TextView, you should ensure you follow these guidelines:

  • Wherever possible, always request soft input when a text field is in focus, whether a physical keyboard is present or not. Let the IME decide whether it’s appropriate to show any input view.
  • Don’t hide soft input for a text field if you expect text input to continue.
  • Don’t start an input connection when you don’t have a text field in focus. Finish the connection when a text field loses focus.
  • Make sure that your view’s implementation of onCheckIsTextEditor() returns true where appropriate. This helps the input method manager service to decide whether soft input should be shown for views that haven’t specified a soft input mode.

Be Careful About Consuming Key Events at the Pre-IME Stage

This is most important when a text field is in focus with the IME active. If some events are consumed early and the IME sees only a subset of them, it may lead to inconsistent behaviour from the IME.  If you handle a KeyEvent before the IME, but don’t indicate that it’s consumed, you could end up with two actions for the same KeyEvent.

Remember also that you should usually aim to consume all KeyEvents in a sequence for a particular key, from initial key press to the subsequent key release, or none of them. The IME should also consume all or none of the events in a sequence, but if it doesn’t receive all of them it may still take action on the ones it does receive.

Consider Initial Focus

With an all-touch device, users are accustomed to tapping in a text field to show the soft input method. This will typically move focus if it’s not already in the field, as well as triggering a request to show soft input.

With a physical keyboard, the user may expect to start typing right away. If the initial focus is not in the right text field, typing may have no effect. If the focus is in the text field, but soft input has not been requested, the IME may not be able to provide normal behaviour for physical key input, since it doesn’t have sufficient information to determine that text editing is really happening.

Consider Movement Keys and Focus Changes

Many built-in reduced mobile keyboards don’t have arrow keys, but some do, and most external keyboards do. As newer Android tablets enter the market with more complete keyboard accessories intended for everyday use, full keyboards are going to be used more often.

Down, up, left and right arrow keys map to DPAD Android keycodes, specifically:

For most standard text views, key events with these key codes moves the cursor one position in the corresponding direction. If moving up from a position in the top line but not at the start of the line, another UP key typically moves the cursor to the start of the field, and moving down from a position in the last line that’s not at the end of the field causes the cursor to jump to the end. If the events have a shift metastate (usually due to holding the shift key) they typically cause selection to be extended or reduced.

If the cursor is already at the start of the field, moving UP (without shift) typically moves focus to the previous focusable field, while moving DOWN from the last position in the field typically moves focus to the next field. Horizontal movement past the start or end of the field can be a little less predictable, and whether it causes a change of focus can depend on the length of the contents of the current and other fields.

In addition to movement key events coming from physical keyboards, some IMEs can generate movement key events, potentially using virtual arrow keys on a soft input view, or converting other input events to movement keys. The keyboard on PRIV can provide cursor movement based on finger movement on the touch-enabled keyboard, for example.

Movement keys originating from the IME can provide some additional complications. These keys are often injected as KeyEvents on the input connection, and thus can only be sent on a valid input connection. If you allow movement out of a text field to an adjacent non-text field, you may allow the user to navigate out of the field using the IME, but they may have to tap back in the field to move focus back and the IME is also likely to be hidden as a consequence of the loss of focus in the text field. Note that some IMEs may provide horizontal movement using the InputConnection.setSelection() method, which doesn’t move between fields. However, it is very difficult for an IME to provide equivalent behavior for vertical movement with no knowledge of line wrapping.

As an application developer, you should consider which focus transitions make sense. Views that shouldn’t ever take focus may need to be marked as such, which can be done using the android:focusable attribute in a layout XML.

If you have a text view in your layout that should never lose focus as a result of movement keys, you can ensure this behaviour by applying an appropriate movement method to the view. In the following code fragment, the ArrowKeyMovementMethod is extended to limit movement to the down direction only by consuming movement events in all other directions, whether or not they change the cursor position in the field. A similar override for the down() method would ensure that no movement out of the field is possible with arrow keys. Within the fragment XML, the EditText is defined with only basic attributes.

public static class BasicFragment extends Fragment {
 
    public PlaceholderFragment() {
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_my_basic_layout,
                container, false);
        final TextView myTextBox = (TextView) rootView.findViewById(R.id.awesomeTextField);
        myTextBox.setMovementMethod(new FocusMoveDownOnlyArrowKeyMethod());
        return rootView;
    }
}
 
private static class FocusMoveDownOnlyArrowKeyMethod extends ArrowKeyMovementMethod {
    @Override
    protected boolean left(TextView widget, Spannable buffer) {
        super.left(widget, buffer); // ignore return code
        // The parent implementation returns false if the movement could not
        // move the cursor due to being at the start or end of the field.
        // Returning true here means that the movement will go no further,
        // and will not cause a change of focus.
        return true;
    }
 
    @Override
    protected boolean right(TextView widget, Spannable buffer) {
        super.right(widget, buffer); // ignore return code
        return true;
    }
 
    @Override
    protected boolean up(TextView widget, Spannable buffer) {
        super.up(widget, buffer); // ignore return code
        return true;
    }
}

Handle the Enter Key as an Editor Action

If your text field defines an editor action for the EditorInfo applied to its InputConnection, you should almost always handle an enter key as if the IME had invoked the action. In most cases where an editor action is defined, the user is likely to expect the action to take effect when pressing an enter key.

This behaviour will be inherited by views derived from TextView, but if you’ve got a custom view that is not a descendent of TextView, or if you’ve overridden onKeyDown() without deferring to the parent implementation for this key you may need to implement this behaviour yourself.

Many IMEs do not consume physical key events, and some may not consume enter key events even if they consume other key events.

Important Information for IME Developers

Our discussion so far has covered what a typical Android application developer would need to know about the PRIV keyboard.  The next section focuses on developing your own IME or virtual keyboard.

Deciding Which Keyboard to Use

Android also allows users to specify that they always wish to show the on-screen keyboard, even if a physical keyboard is available.  This toggle is at the top of the input method chooser dialog. This setting changes the Configuration presented to activities and services to match the appearance of no physical keyboard being available: Configuration.keyboard is set to KEYBOARD_NO_KEYS, and Configuration,hardKeyboardHidden is set to HARDKEYBOARDHIDDEN_YES, regardless of whether the slider is open or closed or an external keyboard is attached or not. In this mode, key presses on a physical keyboard follow the same input pipeline. In the image below, the toggle is turned on.

keyboard

Typically an application developer should not use the presence of a physical keyboard to control whether or not soft input is requested for text input. The active Input Method Editor (IME) may provide enhanced handling of physical key input, but may rely on having soft input requested to determine if it’s appropriate to consume key events. To provide the most consistent input experience, it is better to always request soft input for text editing and let the IME determine whether or not it is appropriate to provide any on-screen view based on the current configuration.

Distinguishing the Built-in Keyboard

The interaction with a built-in keyboard on a mobile device is different in significant ways from that with a typical external keyboard.

  • The built-in keyboard almost always has fewer and smaller keys than a typical external keyboard. This may require some on-screen affordances to provide access to characters that could be entered with a full external keyboard.
  • The built-in keyboard is usually very close to the display, and thus it may be practical to combine interaction with UI elements on the touch screen with keyboard input. In contrast, an external keyboard may be far enough away to make interaction with the touch screen awkward.

Given these differences, it may be desirable for an IME to handle keys from a built-in keyboard differently from those from an external keyboard.

The InputDevice API does not provide a method to distinguish a built-in from an external keyboard in general. However, as documented in the Keyboard Configuration section of the keyboard devices document, a built-in keyboard is always assigned a device ID of 0, in order to maintain compatibility with the deprecated KeyCharacterMap.BUILT_IN_KEYBOARD field. Although the intent of that deprecation appears to be to discourage app developers from making assumptions about the available input devices, the clear device ID is still very useful for IMEs which need to make a distinction between built-in and external keyboards.  For any particular KeyEvent, you can determine the ID of the source device using KeyEvent.getDeviceId().

An application developer may not need to distinguish between internal and external devices, but it may still be a consideration if you’re trying to combine on-screen controls and key presses. It may also be important to consider orientation for the built-in keyboard differently than for an external one.  The keyboard on a portrait slider is difficult to use for text input with the device in a landscape orientation.

Distinguishing Multiple Devices

It may be that you want to determine which or how many keyboards are present in advance of receiving any key events. For example, as an IME, you may wish to provide no UI if an external keyboard is available, regardless of the presence of a built-in keyboard.  You can use the InputDevice API to get information about currently present devices. The following code block shows an example in which alphabetic keyboards are identified.

final int deviceIds[] = InputDevice.getDeviceIds();
for (final int id : deviceIds) {
    final InputDevice device = InputDevice.getDevice(id);
    if (device != null)
            && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
        if (id == 0) {
            // Do something with a built-in alphabetic keyboard.
        } else {
            // Do something with an external keyboard.
        }
    }
}

Note that the InputDevice API doesn’t tell you if a built-in keyboard is hidden or not. While there’s only one keyboard device present, you can correlate with the information provided in the Configuration, but if there is an internal keyboard as well as an external one, you won’t be able to tell the slider position from the Configuration.

Android Key Event Flow

The flow of key events is described in Android developer documentation for keyboard devices – in particular refer to the Keyboard Operation section. Typically as an Input Method Editor (IME) or app developer you won’t need to understand the details as far as the InputDispatcher, but you should be aware of the subsequent stages through which a key event passes.  In particular, note that for the application with the focused window, a key event can be consumed before the IME has an opportunity to see it, through views overriding View.dispatchKeyEventPreIme(KeyEvent) or View.onKeyPreIme(int, KeyEvent). This might be done to prevent the IME from hiding on a back key press if the app needs to do something different, although any key event could be handled in this way.

After the pre-IME dispatch, if the event was not consumed, the active IME has an opportunity to handle the events. Many IMEs don’t handle physical key events at all, leaving them for the app to process, although the default implementations of InputMethodService.onKeyDown(int, KeyEvent) and InputMethodService.onKeyUp(int, KeyEvent) together handle back key events in order to dismiss soft input if showing.

If the IME has not handled a key event, it propagates again through the view hierarchy to the focused view where it may be handled. Typically for text views, this is done through a key listener converting key events into edits on the associated editor; in many cases this will be an instance of QwertyKeyListener for standard text views.

Why might an IME consume key events?

If physical key events can be handled by an application’s views, and provide text input in standard text views without the app developer having to do anything extra, you might wonder why an IME developer should ever bother intercepting them. Simply put, there are opportunities to provide a better typing experience, particularly for a small built-in keyboard.

Auto-Correction, Auto-Completion and Suggestions

You may want to provide auto-correction and auto-completion when the user types a space or punctuation key. This may be less important for a full-sized PC keyboard than a small keyboard on a mobile device, but on a small keyboard where accuracy may be reduced such functionality can significantly enhance typing efficiency.

In addition, you may wish to provide suggestions for the next word or alternatives to auto-correction, and possibly have keyboard actions select from those suggestions.

The IME could react to changes in the text without handling the keys, using InputConnection APIs to track changes, but without processing the keys, it can be tricky to distinguish that changes in the text came directly from typing on the keyboard, and thus to be sure that it’s appropriate to apply auto-correction.

Enhanced Handling of Key Long Press and Meta Keys

The standard Android behaviour for client views derived from TextView (or which at least use QwertyKeyListener) brings up a symbol picker dialog for a long press on keys with associated accents for keyboards of the type typical on a mobile device. This results in a few issues:

  • Accent lists are hard coded into the framework and may not be complete for the current input language.
  • Using an on-screen dialog in the middle of the screen for some characters can be very disruptive to typing flow, and providing a way to access accents without the user having to move their thumbs far off the keyboard can be more efficient.
  • Not all input clients provide the behaviour provided by QwertyKeyListener. WebViews, for example, typically provides repeated characters for key long-press actions. It may cause frustration to users if they can’t rely on the keyboard behaving the same way in different text fields.

Other behaviour on long-press actions may also be desirable, for example providing an easy way to capitalize a single letter.

The default key listeners provide some handling of meta keys (i.e shift and alt), including:

  • A “sticky” mode for which a single press and release of a meta key followed by a single press and release of character key will act as if the meta key had been held for the second key press.
  • A locked mode, where two presses and releases of a meta key cause subsequent character key presses to act as if the meta key was held, until the same meta key is pressed again.

As with the long-press handling, not all clients provide this processing of metastate, so the behavior can seem inconsistent. There’s also no standard mechanism to provide any visual clues of locked or sticky state, so the user may not know what effect is applied to the next key stroke.

If the IME handles raw key events, it is able to overcome all of the above issues and provides a consistent experience across different input clients. It can also provide special functions on key combinations or on specific special keys, such as showing an on-screen symbol keyboard when pressing the sym key.

The Challenge of Consistency

Although an IME can provide an enhanced user experience for text input by consuming raw key events and using the InputConnection APIs to commit text, it should not consume key events when the user is not entering text. Doing so could break applications that expect to handle raw key events directly, such as for shortcuts or game controls. Showing any input or candidates view outside of text input could also be disruptive.

So how can an IME know that there’s really an active input connection? In general, if the client has requested that soft input be shown, the IME should be able to assume that text input is occurring. It is more problematic if the app does not request soft input at all, or if it is hidden while a text field has focus.

You’ll typically find that your IME’s onStartInput() method is called every time the user switches to a new app, getCurrentInputStarted() will always return true and getCurrentInputConnection() will always return a non-null InputConnection. These are thus insufficient tests for an active connection.

For most apps, the IME can actually work out indirectly that an input connection was started by the client, using a technique like the following.  This relies on behavior that’s not documented in Android APIs, but it seems to work most of the time.

final InputBinding binding = getCurrentInputBinding();
final InputConnection connection = getCurrentInputConnection();
final boolean isThisReallyAnActiveConnection  = connection != null
        && binding != null
        && binding.getConnection() != connection;

Unfortunately, this isn’t always reliable. A small number of applications, notably some of the most popular Android browsers, will always have an active input connection, regardless of whether a text field is in focus. A new connection may be started every time the user moves focus to a text field, but that connection may not be finished when focus moves out of the text field.

Solving this problem in the IME alone is very tricky, but you may be able to use some history, such as if the input view was ever shown for the current input connection and subsequently hidden (particularly if you know it was hidden with the back key), it may be appropriate for the IME to show itself on a subsequent key press.

About MSohm

Mark Sohm has been helping developers create applications for BlackBerry devices for over 10 years, going all the way back to the very first BlackBerry JDK on BlackBerry OS 3.6.

Join the conversation

Show comments Hide comments
+ -
blog comments powered by Disqus