Search code examples
androidandroid-softkeyboardandroid-keypadandroid-inputtype

How to show Android keyboard with symbols mode by default?


I have a EditText component, and, of course, if you click on it, the Android keypad is shown, allowing the user to input text. As far as I know, all Android software keyboards have (at least) a letter mode (ABC) and a symbols mode (?123). Their default view is the letter mode.

Now when the keypad is shown when the EditText component is clicked, I want the symbols mode to be shown by default. The user will still be able to switch to the letter mode.

enter image description here

Is there a way to achieve that? If yes, how?


Solution

  • I'm posting this because I don't think any of the answers actually address the question. The screenshot in the question does not correspond to a particular InputType's default state. So, switching InputTypes will not give you the layout from the screenshot.

    (based on my research...)

    Support for symbol input is not governed by any contract. One can very well leave symbols out when creating their own InputMethod. OR, they can add pagination support to provide access to 100s of symbols. Can this be bound by a contract? May be. But, it isn't at present.

    Input method framework does not allow direct communication between the client and the IME. All communication happens either through the InputMethodManager or through InputConnection — a one-way channel. Switching to symbols using ?123 is, however, an internal event — not a defined state/action. Client applications cannot switch to it. There's no public (or hidden) API to make this happen.

    InputType indicates something entirely different to an IME. Not sure why everyone is recommending its use. You may of course find that a particular InputType provides most of the required keys. But that isn't the same as show[ing] Android keyboard with symbols mode by default.

    Possible workaround:

    We'll create a custom EditText. We don't have to. It'll just keep everything in one place, and save us from copy-paste nightmare.

    public class CusEditText extends EditText {
    
        private final int mDefinedActionId;
    
        public CusEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
             
            // Corresponds to 'android:imeActionId' value
            mDefinedActionId = getResources().getInteger(R.integer.definedActionId);
    
            setOnEditorActionListener(new OnEditorActionListener() {
            
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    Log.i("CusEditText", "onEditorAction, actionId = " + actionId);
    
                    // Only bother if (...)
                    if (actionId == mDefinedActionId) {
    
                        // Check if current InputType is NUMBER
                        if ((getInputType() & InputType.TYPE_CLASS_NUMBER) != 0) {
                            // Toggle
                            setImeActionLabel("NUM", mDefinedActionId);
                            setInputType(InputType.TYPE_CLASS_TEXT);
                        } else {
                            // Current InputType is TEXT // Toggle
                            setImeActionLabel("ABC", mDefinedActionId);
                            setInputType(InputType.TYPE_CLASS_NUMBER);
                        }
    
                        // We've handled this
                        return true;
                    }
                
                    // Let someone else worry about this
                    return false;
                }
            });
        }
    }
    

    Next, we need to define definedActionId. Open or create res/values/integers.xml and add:

    <integer name="definedActionId">-100</integer>
    

    -100 is an arbitrary value. I checked EditorInfo and the actionIds (IME_ACTION_XXXX) were >= 0. -100 seems like a good candidate.

    In xml, your layout will look like:

    <com.your.packagename.CusEditText
        android:layout_width="blah"
        android:layout_height="blah"
        android:inputType="number"
        android:imeActionId="@integer/definedActionId"
        android:imeActionLabel="ABC"/>
    
    <!-- Probably use @string resource in place of ABC -->
    

    There's not much to explain. IME will launch in NUMBER mode. Instead of a checkmark icon, it'll display ABC. On click, we intercept the actionId and toggle between NUMBER and TEXT input. We're using setInputType(...) because it not only updates the InputType, it also restarts the IME with changes. setRawInputType(...) only updates the InputType.

    Issues:

    As you can tell, this isn't really a solution. If the user closes the keyboard(using the back button) in TEXT mode, the keyboard will remain in the TEXT mode when they open it again. To go to the NUMBER mode, user will have to click NUM. Also, in TEXT mode, user will see NUM as the action, along with ?123 option. This doesn't break anything, but does take away from the UX.

    We can't do anything about ?123 showing in TEXT mode for reasons listed above. But, we can try to make sure that the keyboard always opens in the NUMBER mode. I'll provide a rough sketch of how we'll do that. Its not straight-forward since we (developers) are not privy to events such as keyboard closing or opening. Updated CusEditText:

    public class CusEditText extends EditText {
    
        private final int mDefinedActionId;
        private long mLastEditorActionTime = 0L;
    
        public CusEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
             
            // Corresponds to 'android:imeActionId' value
            mDefinedActionId = getResources().getInteger(R.integer.definedActionId);
    
            setOnEditorActionListener(new OnEditorActionListener() {
            
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    Log.i("CusEditText", "onEditorAction, actionId = " + actionId);
    
                    // Only bother if (...)
                    if (actionId == mDefinedActionId) {
    
                        // setInputType(...) will restart the IME
                        // and call finishComposingText() 
                        // see below
                        mLastEditorActionTime = SystemClock.elapsedRealtime();
    
                        // Check if current InputType is NUMBER
                        if ((getInputType() & InputType.TYPE_CLASS_NUMBER) != 0) {
                            // Toggle
                            setImeActionLabel("NUM", mDefinedActionId);
                            setInputType(InputType.TYPE_CLASS_TEXT);
                        } else {
                            // Current InputType is TEXT // Toggle
                            setImeActionLabel("ABC", mDefinedActionId);
                            setInputType(InputType.TYPE_CLASS_NUMBER);
                        }
    
                        // We've handled this
                        return true;
                    }
                
                    // Let someone else worry about this
                    return false;
                }
            });
        }
    
        @Override
        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
            InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
    
            return new CusInputConnectionWrapper(inputConnection, false);
        }
    
        private class CusInputConnectionWrapper extends InputConnectionWrapper {
            private CusInputConnectionWrapper(InputConnection target, boolean mutable) {
                super(target, mutable);
            }
    
            @Override
            public boolean finishComposingText() {
                Log.i("CICW", "finishComposingText");
    
                // Ignore finishComposingText for 1 second (1000L)
                if (SystemClock.elapsedRealtime() - mLastEditorActionTime > 1000L) {
                    if ((getInputType() & InputType.TYPE_CLASS_NUMBER) == 0) {
                        // InputConnection is no longer valid.
                        // Switch back to NUMBER iff required
                        setImeActionLabel("ABC", mDefinedActionId);
                        setInputType(InputType.TYPE_CLASS_NUMBER);
                    }
                }
    
                return super.finishComposingText();
            }
        }
    }
    

    Again, code is self-explanatory. We create a InputConnectionWrapper and listen for the finishComposingText() callback. If we're manually switching between TEXT and NUMBER, we use a flag since finishComposingText() will automatically be called. Else, we check if input type is set to TEXT and change it to NUMBER. I am not sure if finishComposingText() is the right method for interpreting keyboard closing/opening. Testing on API 21, vanilla android, this seems to work. More tests will be required.

    I really hope someone can come up with a better, more robust solution than this - or modify my workaround so that it doesn't look like one.

    Summary

    Task at hand is to provide functionality of switching between NUMBER & TEXT input modes around existing Input Method Engines (IMEs). The first approach was to use imeActionLabel & imeActionId in the switching mechanism. This approach worked well with Google's keyboard (this is the imeActionLabel), but failed with Samsung's - imeActionLabel failed to show up in portrait (without extract). Possible workaround is to include the toggle button in the app's own UI.

    Even with Google's keyboard, the letters (text) fail to show up when the mode switches back to NUMBER after inputting letters. This problem was fixed (at least on tested devices) by using flag flagNoExtractUi which prevents the IME from entering fullscreen mode in landscape orientation.

    Final solution (pending implementation & testing)

    • The IME starts in the NUMBER input mode (95% use-cases involve number input)
    • A button is added to app's UI (next to the EditText) for switching between NUMBER & TEXT mode
    • User can switch from NUMBER to TEXT without any restrictions. Switching back from TEXT to NUMBER requires that no alphabets have been added.
    • InputType is preserved between keyboard closing & reopening. Example: If the user switches to TEXT mode and closes the keyboard, it will open in the TEXT mode. The InputType is not reset.

    For more information about the approaches tried, refer to this discussion thread.

    Screenshots

    Default (NUMBER):

    enter image description here

    Switched to TEXT:

    enter image description here

    Recorded video link