diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
| commit | 52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98 (patch) | |
| tree | 2eb01432fa3da41550dd622d29fb672509122b51 /samples/SoftKeyboard | |
| parent | d4aee0c0caa00aa02d4c50ed28151591ac0456b5 (diff) | |
| download | android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.gz android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.bz2 android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.zip | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'samples/SoftKeyboard')
21 files changed, 1566 insertions, 0 deletions
diff --git a/samples/SoftKeyboard/Android.mk b/samples/SoftKeyboard/Android.mk new file mode 100755 index 000000000..883bf2fd7 --- /dev/null +++ b/samples/SoftKeyboard/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := samples + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_SDK_VERSION := current + +LOCAL_PACKAGE_NAME := SoftKeyboard + +include $(BUILD_PACKAGE) diff --git a/samples/SoftKeyboard/AndroidManifest.xml b/samples/SoftKeyboard/AndroidManifest.xml new file mode 100755 index 000000000..61b513109 --- /dev/null +++ b/samples/SoftKeyboard/AndroidManifest.xml @@ -0,0 +1,12 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.softkeyboard"> + <application android:label="@string/ime_name"> + <service android:name="SoftKeyboard" + android:permission="android.permission.BIND_INPUT_METHOD"> + <intent-filter> + <action android:name="android.view.InputMethod" /> + </intent-filter> + <meta-data android:name="android.view.im" android:resource="@xml/method" /> + </service> + </application> +</manifest> diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_delete.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_delete.png Binary files differnew file mode 100644 index 000000000..6cee59682 --- /dev/null +++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_delete.png diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_done.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_done.png Binary files differnew file mode 100755 index 000000000..c0d6d1394 --- /dev/null +++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_done.png diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_return.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_return.png Binary files differnew file mode 100644 index 000000000..cbe2b152f --- /dev/null +++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_return.png diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_search.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_search.png Binary files differnew file mode 100755 index 000000000..127755d6b --- /dev/null +++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_search.png diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_shift.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_shift.png Binary files differnew file mode 100644 index 000000000..d05962846 --- /dev/null +++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_shift.png diff --git a/samples/SoftKeyboard/res/drawable/sym_keyboard_space.png b/samples/SoftKeyboard/res/drawable/sym_keyboard_space.png Binary files differnew file mode 100644 index 000000000..09b94d9e6 --- /dev/null +++ b/samples/SoftKeyboard/res/drawable/sym_keyboard_space.png diff --git a/samples/SoftKeyboard/res/layout/input.xml b/samples/SoftKeyboard/res/layout/input.xml new file mode 100755 index 000000000..1b57468df --- /dev/null +++ b/samples/SoftKeyboard/res/layout/input.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> + +<com.example.android.softkeyboard.LatinKeyboardView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyboard" + android:layout_alignParentBottom="true" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + /> diff --git a/samples/SoftKeyboard/res/values-land/dimens.xml b/samples/SoftKeyboard/res/values-land/dimens.xml new file mode 100644 index 000000000..b5f3bc128 --- /dev/null +++ b/samples/SoftKeyboard/res/values-land/dimens.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> + +<resources> + <dimen name="key_height">46dip</dimen> +</resources> diff --git a/samples/SoftKeyboard/res/values/colors.xml b/samples/SoftKeyboard/res/values/colors.xml new file mode 100644 index 000000000..74d103af0 --- /dev/null +++ b/samples/SoftKeyboard/res/values/colors.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> +<resources> + <color name="candidate_normal">#FF000000</color> + <color name="candidate_recommended">#FFE35900</color> + <color name="candidate_other">#ff808080</color> + <color name="candidate_background">#bbffffff</color> +</resources>
\ No newline at end of file diff --git a/samples/SoftKeyboard/res/values/dimens.xml b/samples/SoftKeyboard/res/values/dimens.xml new file mode 100644 index 000000000..caf615cfa --- /dev/null +++ b/samples/SoftKeyboard/res/values/dimens.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> + +<resources> + <dimen name="key_height">50dip</dimen> + <dimen name="candidate_font_height">16sp</dimen> + <dimen name="candidate_vertical_padding">6sp</dimen> +</resources>
\ No newline at end of file diff --git a/samples/SoftKeyboard/res/values/strings.xml b/samples/SoftKeyboard/res/values/strings.xml new file mode 100644 index 000000000..bc645b2c0 --- /dev/null +++ b/samples/SoftKeyboard/res/values/strings.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Title for Latin keyboard --> + <string name="ime_name">Sample Soft Keyboard</string> + + <!-- Symbols that are commonly considered word separators in this language --> + <string name="word_separators">\u0020.,;:!?\n()[]*&@{}/<>_+=|"</string> + + <!-- Labels on soft keys --> + <string name="label_go_key">Go</string> + <string name="label_next_key">Next</string> + <string name="label_send_key">Send</string> +</resources> diff --git a/samples/SoftKeyboard/res/xml/method.xml b/samples/SoftKeyboard/res/xml/method.xml new file mode 100644 index 000000000..d246624c4 --- /dev/null +++ b/samples/SoftKeyboard/res/xml/method.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2008, 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. + */ +--> + +<!-- The attributes in this XML file provide configuration information --> +<!-- for the Search Manager. --> + +<input-method xmlns:android="http://schemas.android.com/apk/res/android" /> diff --git a/samples/SoftKeyboard/res/xml/qwerty.xml b/samples/SoftKeyboard/res/xml/qwerty.xml new file mode 100755 index 000000000..e81d9f102 --- /dev/null +++ b/samples/SoftKeyboard/res/xml/qwerty.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> + +<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="@dimen/key_height" + > + + <Row> + <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/> + <Key android:codes="119" android:keyLabel="w"/> + <Key android:codes="101" android:keyLabel="e"/> + <Key android:codes="114" android:keyLabel="r"/> + <Key android:codes="116" android:keyLabel="t"/> + <Key android:codes="121" android:keyLabel="y"/> + <Key android:codes="117" android:keyLabel="u"/> + <Key android:codes="105" android:keyLabel="i"/> + <Key android:codes="111" android:keyLabel="o"/> + <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/> + </Row> + + <Row> + <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p" + android:keyEdgeFlags="left"/> + <Key android:codes="115" android:keyLabel="s"/> + <Key android:codes="100" android:keyLabel="d"/> + <Key android:codes="102" android:keyLabel="f"/> + <Key android:codes="103" android:keyLabel="g"/> + <Key android:codes="104" android:keyLabel="h"/> + <Key android:codes="106" android:keyLabel="j"/> + <Key android:codes="107" android:keyLabel="k"/> + <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/> + </Row> + + <Row> + <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" + android:keyWidth="15%p" android:isModifier="true" + android:isSticky="true" android:keyEdgeFlags="left"/> + <Key android:codes="122" android:keyLabel="z"/> + <Key android:codes="120" android:keyLabel="x"/> + <Key android:codes="99" android:keyLabel="c"/> + <Key android:codes="118" android:keyLabel="v"/> + <Key android:codes="98" android:keyLabel="b"/> + <Key android:codes="110" android:keyLabel="n"/> + <Key android:codes="109" android:keyLabel="m"/> + <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" + android:keyWidth="15%p" android:keyEdgeFlags="right" + android:isRepeatable="true"/> + </Row> + + <Row android:rowEdgeFlags="bottom"> + <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" + android:keyWidth="20%p" android:keyEdgeFlags="left"/> + <Key android:codes="-2" android:keyLabel="123" android:keyWidth="15%p"/> + <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" + android:keyWidth="30%p" android:isRepeatable="true"/> + <Key android:codes="46,44" android:keyLabel=". ," + android:keyWidth="15%p"/> + <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" + android:keyWidth="20%p" android:keyEdgeFlags="right"/> + </Row> +</Keyboard> +
\ No newline at end of file diff --git a/samples/SoftKeyboard/res/xml/symbols.xml b/samples/SoftKeyboard/res/xml/symbols.xml new file mode 100755 index 000000000..a28d752bd --- /dev/null +++ b/samples/SoftKeyboard/res/xml/symbols.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> + +<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="@dimen/key_height" + > + + <Row> + <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/> + <Key android:codes="50" android:keyLabel="2"/> + <Key android:codes="51" android:keyLabel="3"/> + <Key android:codes="52" android:keyLabel="4"/> + <Key android:codes="53" android:keyLabel="5"/> + <Key android:codes="54" android:keyLabel="6"/> + <Key android:codes="55" android:keyLabel="7"/> + <Key android:codes="56" android:keyLabel="8"/> + <Key android:codes="57" android:keyLabel="9"/> + <Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/> + </Row> + + <Row> + <Key android:codes="64" android:keyLabel="\@" android:keyEdgeFlags="left"/> + <Key android:codes="35" android:keyLabel="\#"/> + <Key android:codes="36" android:keyLabel="$"/> + <Key android:codes="37" android:keyLabel="%"/> + <Key android:codes="38" android:keyLabel="&"/> + <Key android:codes="42" android:keyLabel="*"/> + <Key android:codes="45" android:keyLabel="-"/> + <Key android:codes="61" android:keyLabel="="/> + <Key android:codes="40" android:keyLabel="("/> + <Key android:codes="41" android:keyLabel=")" android:keyEdgeFlags="right"/> + </Row> + + <Row> + <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" + android:keyWidth="15%p" android:isModifier="true" + android:isSticky="true" android:keyEdgeFlags="left"/> + <Key android:codes="33" android:keyLabel="!" /> + <Key android:codes="34" android:keyLabel="""/> + <Key android:codes="39" android:keyLabel="\'"/> + <Key android:codes="58" android:keyLabel=":"/> + <Key android:codes="59" android:keyLabel=";"/> + <Key android:codes="47" android:keyLabel="/" /> + <Key android:codes="63" android:keyLabel="\?"/> + <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" + android:keyWidth="15%p" android:keyEdgeFlags="right" + android:isRepeatable="true"/> + </Row> + + <Row android:rowEdgeFlags="bottom"> + <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" + android:keyWidth="20%p" android:keyEdgeFlags="left" /> + <Key android:codes="-2" android:keyLabel="ABC" android:keyWidth="15%p" /> + <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" + android:isRepeatable="true"/> + <Key android:codes="44" android:keyLabel="," android:keyWidth="15%p" /> + <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" + android:keyWidth="20%p" android:keyEdgeFlags="right" + /> + </Row> +</Keyboard>
\ No newline at end of file diff --git a/samples/SoftKeyboard/res/xml/symbols_shift.xml b/samples/SoftKeyboard/res/xml/symbols_shift.xml new file mode 100755 index 000000000..d7139f340 --- /dev/null +++ b/samples/SoftKeyboard/res/xml/symbols_shift.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2008, 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. +*/ +--> + +<Keyboard xmlns:android="http://schemas.android.com/apk/res/android" + android:keyWidth="10%p" + android:horizontalGap="0px" + android:verticalGap="0px" + android:keyHeight="@dimen/key_height" + > + + <Row> + <Key android:codes="126" android:keyLabel="~" android:keyEdgeFlags="left"/> + <Key android:codes="177" android:keyLabel="±"/> + <Key android:codes="215" android:keyLabel="×"/> + <Key android:codes="247" android:keyLabel="÷"/> + <Key android:codes="8226" android:keyLabel="•"/> + <Key android:codes="176" android:keyLabel="°"/> + <Key android:codes="96" android:keyLabel="`"/> + <Key android:codes="180" android:keyLabel="´"/> + <Key android:codes="123" android:keyLabel="{"/> + <Key android:codes="125" android:keyLabel="}" android:keyEdgeFlags="right"/> + </Row> + + <Row> + <Key android:codes="169" android:keyLabel="©" android:keyEdgeFlags="left"/> + <Key android:codes="163" android:keyLabel="£"/> + <Key android:codes="8364" android:keyLabel="€"/> + <Key android:codes="94" android:keyLabel="^"/> + <Key android:codes="174" android:keyLabel="®"/> + <Key android:codes="165" android:keyLabel="¥"/> + <Key android:codes="95" android:keyLabel="_"/> + <Key android:codes="43" android:keyLabel="+"/> + <Key android:codes="91" android:keyLabel="["/> + <Key android:codes="93" android:keyLabel="]" android:keyEdgeFlags="right"/> + </Row> + + <Row> + <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" + android:keyWidth="15%p" android:isModifier="true" + android:isSticky="true" android:keyEdgeFlags="left"/> + <Key android:codes="161" android:keyLabel="¡" /> + <Key android:codes="60" android:keyLabel="<"/> + <Key android:codes="62" android:keyLabel=">"/> + <Key android:codes="162" android:keyLabel="¢"/> + <Key android:codes="124" android:keyLabel="|"/> + <Key android:codes="92" android:keyLabel="\\" /> + <Key android:codes="191" android:keyLabel="¿"/> + <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" + android:keyWidth="15%p" android:keyEdgeFlags="right" + android:isRepeatable="true"/> + </Row> + + <Row android:rowEdgeFlags="bottom"> + <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" + android:keyWidth="20%p" android:keyEdgeFlags="left" /> + <Key android:codes="-2" android:keyLabel="ABC" android:keyWidth="15%p" /> + <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" android:keyWidth="30%p" + android:isRepeatable="true"/> + <Key android:codes="8230" android:keyLabel="…" android:keyWidth="15%p" /> + <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" + android:keyWidth="20%p" android:keyEdgeFlags="right" /> + </Row> +</Keyboard> diff --git a/samples/SoftKeyboard/src/com/example/android/softkeyboard/CandidateView.java b/samples/SoftKeyboard/src/com/example/android/softkeyboard/CandidateView.java new file mode 100755 index 000000000..7cadead8a --- /dev/null +++ b/samples/SoftKeyboard/src/com/example/android/softkeyboard/CandidateView.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * 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 com.example.android.softkeyboard; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import java.util.ArrayList; +import java.util.List; + +public class CandidateView extends View { + + private static final int OUT_OF_BOUNDS = -1; + + private SoftKeyboard mService; + private List<String> mSuggestions; + private int mSelectedIndex; + private int mTouchX = OUT_OF_BOUNDS; + private Drawable mSelectionHighlight; + private boolean mTypedWordValid; + + private Rect mBgPadding; + + private static final int MAX_SUGGESTIONS = 32; + private static final int SCROLL_PIXELS = 20; + + private int[] mWordWidth = new int[MAX_SUGGESTIONS]; + private int[] mWordX = new int[MAX_SUGGESTIONS]; + + private static final int X_GAP = 10; + + private static final List<String> EMPTY_LIST = new ArrayList<String>(); + + private int mColorNormal; + private int mColorRecommended; + private int mColorOther; + private int mVerticalPadding; + private Paint mPaint; + private boolean mScrolled; + private int mTargetScrollX; + + private int mTotalWidth; + + private GestureDetector mGestureDetector; + + /** + * Construct a CandidateView for showing suggested words for completion. + * @param context + * @param attrs + */ + public CandidateView(Context context) { + super(context); + mSelectionHighlight = context.getResources().getDrawable( + android.R.drawable.list_selector_background); + mSelectionHighlight.setState(new int[] { + android.R.attr.state_enabled, + android.R.attr.state_focused, + android.R.attr.state_window_focused, + android.R.attr.state_pressed + }); + + Resources r = context.getResources(); + + setBackgroundColor(r.getColor(R.color.candidate_background)); + + mColorNormal = r.getColor(R.color.candidate_normal); + mColorRecommended = r.getColor(R.color.candidate_recommended); + mColorOther = r.getColor(R.color.candidate_other); + mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding); + + mPaint = new Paint(); + mPaint.setColor(mColorNormal); + mPaint.setAntiAlias(true); + mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height)); + mPaint.setStrokeWidth(0); + + mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + mScrolled = true; + int sx = getScrollX(); + sx += distanceX; + if (sx < 0) { + sx = 0; + } + if (sx + getWidth() > mTotalWidth) { + sx -= distanceX; + } + mTargetScrollX = sx; + scrollTo(sx, getScrollY()); + invalidate(); + return true; + } + }); + setHorizontalFadingEdgeEnabled(true); + setWillNotDraw(false); + setHorizontalScrollBarEnabled(false); + setVerticalScrollBarEnabled(false); + } + + /** + * A connection back to the service to communicate with the text field + * @param listener + */ + public void setService(SoftKeyboard listener) { + mService = listener; + } + + @Override + public int computeHorizontalScrollRange() { + return mTotalWidth; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measuredWidth = resolveSize(50, widthMeasureSpec); + + // Get the desired height of the icon menu view (last row of items does + // not have a divider below) + Rect padding = new Rect(); + mSelectionHighlight.getPadding(padding); + final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding + + padding.top + padding.bottom; + + // Maximum possible width and desired height + setMeasuredDimension(measuredWidth, + resolveSize(desiredHeight, heightMeasureSpec)); + } + + /** + * If the canvas is null, then only touch calculations are performed to pick the target + * candidate. + */ + @Override + protected void onDraw(Canvas canvas) { + if (canvas != null) { + super.onDraw(canvas); + } + mTotalWidth = 0; + if (mSuggestions == null) return; + + if (mBgPadding == null) { + mBgPadding = new Rect(0, 0, 0, 0); + if (getBackground() != null) { + getBackground().getPadding(mBgPadding); + } + } + int x = 0; + final int count = mSuggestions.size(); + final int height = getHeight(); + final Rect bgPadding = mBgPadding; + final Paint paint = mPaint; + final int touchX = mTouchX; + final int scrollX = getScrollX(); + final boolean scrolled = mScrolled; + final boolean typedWordValid = mTypedWordValid; + final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent()); + + for (int i = 0; i < count; i++) { + String suggestion = mSuggestions.get(i); + float textWidth = paint.measureText(suggestion); + final int wordWidth = (int) textWidth + X_GAP * 2; + + mWordX[i] = x; + mWordWidth[i] = wordWidth; + paint.setColor(mColorNormal); + if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { + if (canvas != null) { + canvas.translate(x, 0); + mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); + mSelectionHighlight.draw(canvas); + canvas.translate(-x, 0); + } + mSelectedIndex = i; + } + + if (canvas != null) { + if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) { + paint.setFakeBoldText(true); + paint.setColor(mColorRecommended); + } else if (i != 0) { + paint.setColor(mColorOther); + } + canvas.drawText(suggestion, x + X_GAP, y, paint); + paint.setColor(mColorOther); + canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, + x + wordWidth + 0.5f, height + 1, paint); + paint.setFakeBoldText(false); + } + x += wordWidth; + } + mTotalWidth = x; + if (mTargetScrollX != getScrollX()) { + scrollToTarget(); + } + } + + private void scrollToTarget() { + int sx = getScrollX(); + if (mTargetScrollX > sx) { + sx += SCROLL_PIXELS; + if (sx >= mTargetScrollX) { + sx = mTargetScrollX; + requestLayout(); + } + } else { + sx -= SCROLL_PIXELS; + if (sx <= mTargetScrollX) { + sx = mTargetScrollX; + requestLayout(); + } + } + scrollTo(sx, getScrollY()); + invalidate(); + } + + public void setSuggestions(List<String> suggestions, boolean completions, + boolean typedWordValid) { + clear(); + if (suggestions != null) { + mSuggestions = new ArrayList<String>(suggestions); + } + mTypedWordValid = typedWordValid; + scrollTo(0, 0); + mTargetScrollX = 0; + // Compute the total width + onDraw(null); + invalidate(); + requestLayout(); + } + + public void clear() { + mSuggestions = EMPTY_LIST; + mTouchX = OUT_OF_BOUNDS; + mSelectedIndex = -1; + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + + if (mGestureDetector.onTouchEvent(me)) { + return true; + } + + int action = me.getAction(); + int x = (int) me.getX(); + int y = (int) me.getY(); + mTouchX = x; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mScrolled = false; + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + if (y <= 0) { + // Fling up!? + if (mSelectedIndex >= 0) { + mService.pickSuggestionManually(mSelectedIndex); + mSelectedIndex = -1; + } + } + invalidate(); + break; + case MotionEvent.ACTION_UP: + if (!mScrolled) { + if (mSelectedIndex >= 0) { + mService.pickSuggestionManually(mSelectedIndex); + } + } + mSelectedIndex = -1; + removeHighlight(); + requestLayout(); + break; + } + return true; + } + + /** + * For flick through from keyboard, call this method with the x coordinate of the flick + * gesture. + * @param x + */ + public void takeSuggestionAt(float x) { + mTouchX = (int) x; + // To detect candidate + onDraw(null); + if (mSelectedIndex >= 0) { + mService.pickSuggestionManually(mSelectedIndex); + } + invalidate(); + } + + private void removeHighlight() { + mTouchX = OUT_OF_BOUNDS; + invalidate(); + } +} diff --git a/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java b/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java new file mode 100644 index 000000000..179844234 --- /dev/null +++ b/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboard.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * 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 com.example.android.softkeyboard; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.inputmethodservice.Keyboard.Row; +import android.view.inputmethod.EditorInfo; + +public class LatinKeyboard extends Keyboard { + + private Key mEnterKey; + + public LatinKeyboard(Context context, int xmlLayoutResId) { + super(context, xmlLayoutResId); + } + + public LatinKeyboard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + super(context, layoutTemplateResId, characters, columns, horizontalPadding); + } + + @Override + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser) { + Key key = new LatinKey(res, parent, x, y, parser); + if (key.codes[0] == 10) { + mEnterKey = key; + } + return key; + } + + /** + * This looks at the ime options given by the current editor, to set the + * appropriate label on the keyboard's enter key (if it has one). + */ + void setImeOptions(Resources res, int options) { + if (mEnterKey == null) { + return; + } + + switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { + case EditorInfo.IME_ACTION_GO: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_go_key); + break; + case EditorInfo.IME_ACTION_NEXT: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_next_key); + break; + case EditorInfo.IME_ACTION_SEARCH: + mEnterKey.icon = res.getDrawable( + R.drawable.sym_keyboard_search); + mEnterKey.label = null; + break; + case EditorInfo.IME_ACTION_SEND: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_send_key); + break; + default: + mEnterKey.icon = res.getDrawable( + R.drawable.sym_keyboard_return); + mEnterKey.label = null; + break; + } + } + + static class LatinKey extends Keyboard.Key { + + public LatinKey(Resources res, Keyboard.Row parent, int x, int y, XmlResourceParser parser) { + super(res, parent, x, y, parser); + } + + /** + * Overriding this method so that we can reduce the target area for the key that + * closes the keyboard. + */ + @Override + public boolean isInside(int x, int y) { + return super.isInside(x, codes[0] == KEYCODE_CANCEL ? y - 10 : y); + } + } + +} diff --git a/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboardView.java b/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboardView.java new file mode 100644 index 000000000..7464607d8 --- /dev/null +++ b/samples/SoftKeyboard/src/com/example/android/softkeyboard/LatinKeyboardView.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * 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 com.example.android.softkeyboard; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.inputmethodservice.Keyboard.Key; +import android.util.AttributeSet; + +public class LatinKeyboardView extends KeyboardView { + + static final int KEYCODE_OPTIONS = -100; + + public LatinKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected boolean onLongPress(Key key) { + if (key.codes[0] == Keyboard.KEYCODE_CANCEL) { + getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); + return true; + } else { + return super.onLongPress(key); + } + } +} diff --git a/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java b/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java new file mode 100644 index 000000000..656efdfb7 --- /dev/null +++ b/samples/SoftKeyboard/src/com/example/android/softkeyboard/SoftKeyboard.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * 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 com.example.android.softkeyboard; + +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.text.method.MetaKeyKeyListener; +import android.util.Log; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Example of writing an input method for a soft keyboard. This code is + * focused on simplicity over completeness, so it should in no way be considered + * to be a complete soft keyboard implementation. Its purpose is to provide + * a basic example for how you would get started writing an input method, to + * be fleshed out as appropriate. + */ +public class SoftKeyboard extends InputMethodService + implements KeyboardView.OnKeyboardActionListener { + static final boolean DEBUG = false; + + /** + * This boolean indicates the optional example code for performing + * processing of hard keys in addition to regular text generation + * from on-screen interaction. It would be used for input methods that + * perform language translations (such as converting text entered on + * a QWERTY keyboard to Chinese), but may not be used for input methods + * that are primarily intended to be used for on-screen text entry. + */ + static final boolean PROCESS_HARD_KEYS = true; + + private KeyboardView mInputView; + private CandidateView mCandidateView; + private CompletionInfo[] mCompletions; + + private StringBuilder mComposing = new StringBuilder(); + private boolean mPredictionOn; + private boolean mCompletionOn; + private int mLastDisplayWidth; + private boolean mCapsLock; + private long mLastShiftTime; + private long mMetaState; + + private LatinKeyboard mSymbolsKeyboard; + private LatinKeyboard mSymbolsShiftedKeyboard; + private LatinKeyboard mQwertyKeyboard; + + private LatinKeyboard mCurKeyboard; + + private String mWordSeparators; + + /** + * Main initialization of the input method component. Be sure to call + * to super class. + */ + @Override public void onCreate() { + super.onCreate(); + mWordSeparators = getResources().getString(R.string.word_separators); + } + + /** + * This is the point where you can do all of your UI initialization. It + * is called after creation and any configuration change. + */ + @Override public void onInitializeInterface() { + if (mQwertyKeyboard != null) { + // Configuration changes can happen after the keyboard gets recreated, + // so we need to be able to re-build the keyboards if the available + // space has changed. + int displayWidth = getMaxWidth(); + if (displayWidth == mLastDisplayWidth) return; + mLastDisplayWidth = displayWidth; + } + mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty); + mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols); + mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift); + } + + /** + * Called by the framework when your view for creating input needs to + * be generated. This will be called the first time your input method + * is displayed, and every time it needs to be re-created such as due to + * a configuration change. + */ + @Override public View onCreateInputView() { + mInputView = (KeyboardView) getLayoutInflater().inflate( + R.layout.input, null); + mInputView.setOnKeyboardActionListener(this); + mInputView.setKeyboard(mQwertyKeyboard); + return mInputView; + } + + /** + * Called by the framework when your view for showing candidates needs to + * be generated, like {@link #onCreateInputView}. + */ + @Override public View onCreateCandidatesView() { + mCandidateView = new CandidateView(this); + mCandidateView.setService(this); + return mCandidateView; + } + + /** + * This is the main point where we do our initialization of the input method + * to begin operating on an application. At this point we have been + * bound to the client, and are now receiving all of the detailed information + * about the target of our edits. + */ + @Override public void onStartInput(EditorInfo attribute, boolean restarting) { + super.onStartInput(attribute, restarting); + + // Reset our state. We want to do this even if restarting, because + // the underlying state of the text editor could have changed in any way. + mComposing.setLength(0); + updateCandidates(); + + if (!restarting) { + // Clear shift states. + mMetaState = 0; + } + + mPredictionOn = false; + mCompletionOn = false; + mCompletions = null; + + // We are now going to initialize our state based on the type of + // text being edited. + switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) { + case EditorInfo.TYPE_CLASS_NUMBER: + case EditorInfo.TYPE_CLASS_DATETIME: + // Numbers and dates default to the symbols keyboard, with + // no extra features. + mCurKeyboard = mSymbolsKeyboard; + break; + + case EditorInfo.TYPE_CLASS_PHONE: + // Phones will also default to the symbols keyboard, though + // often you will want to have a dedicated phone keyboard. + mCurKeyboard = mSymbolsKeyboard; + break; + + case EditorInfo.TYPE_CLASS_TEXT: + // This is general text editing. We will default to the + // normal alphabetic keyboard, and assume that we should + // be doing predictive text (showing candidates as the + // user types). + mCurKeyboard = mQwertyKeyboard; + mPredictionOn = true; + + // We now look for a few special variations of text that will + // modify our behavior. + int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; + if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) { + // Do not display predictions / what the user is typing + // when they are entering a password. + mPredictionOn = false; + } + + if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { + // Our predictions are not useful for e-mail addresses + // or URIs. + mPredictionOn = false; + } + + if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + // If this is an auto-complete text view, then our predictions + // will not be shown and instead we will allow the editor + // to supply their own. We only show the editor's + // candidates when in fullscreen mode, otherwise relying + // own it displaying its own UI. + mPredictionOn = false; + mCompletionOn = isFullscreenMode(); + } + + // We also want to look at the current state of the editor + // to decide whether our alphabetic keyboard should start out + // shifted. + updateShiftKeyState(attribute); + break; + + default: + // For all unknown input types, default to the alphabetic + // keyboard with no special features. + mCurKeyboard = mQwertyKeyboard; + } + + // Update the label on the enter key, depending on what the application + // says it will do. + mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions); + } + + /** + * This is called when the user is done editing a field. We can use + * this to reset our state. + */ + @Override public void onFinishInput() { + super.onFinishInput(); + + // Clear current composing text and candidates. + mComposing.setLength(0); + updateCandidates(); + + // We only hide the candidates window when finishing input on + // a particular editor, to avoid popping the underlying application + // up and down if the user is entering text into the bottom of + // its window. + setCandidatesViewShown(false); + + mCurKeyboard = mQwertyKeyboard; + if (mInputView != null) { + mInputView.closing(); + } + } + + @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { + super.onStartInputView(attribute, restarting); + // Apply the selected keyboard to the input view. + mInputView.setKeyboard(mCurKeyboard); + mInputView.closing(); + } + + /** + * Deal with the editor reporting movement of its cursor. + */ + @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { + + // If the current selection in the text view changes, we should + // clear whatever candidate text we have. + if (mComposing.length() > 0 && (newSelStart != candidatesEnd + || newSelEnd != candidatesEnd)) { + mComposing.setLength(0); + updateCandidates(); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } + } + } + + /** + * This tells us about completions that the editor has determined based + * on the current text in it. We want to use this in fullscreen mode + * to show the completions ourself, since the editor can not be seen + * in that situation. + */ + @Override public void onDisplayCompletions(CompletionInfo[] completions) { + if (mCompletionOn) { + mCompletions = completions; + if (completions == null) { + setSuggestions(null, false, false); + return; + } + + List<String> stringList = new ArrayList<String>(); + for (int i=0; i<(completions != null ? completions.length : 0); i++) { + CompletionInfo ci = completions[i]; + if (ci != null) stringList.add(ci.getText().toString()); + } + setSuggestions(stringList, true, true); + } + } + + /** + * This translates incoming hard key events in to edit operations on an + * InputConnection. It is only needed when using the + * PROCESS_HARD_KEYS option. + */ + private boolean translateKeyDown(int keyCode, KeyEvent event) { + mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState, + keyCode, event); + int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState)); + mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState); + InputConnection ic = getCurrentInputConnection(); + if (c == 0 || ic == null) { + return false; + } + + boolean dead = false; + + if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) { + dead = true; + c = c & KeyCharacterMap.COMBINING_ACCENT_MASK; + } + + if (mComposing.length() > 0) { + char accent = mComposing.charAt(mComposing.length() -1 ); + int composed = KeyEvent.getDeadChar(accent, c); + + if (composed != 0) { + c = composed; + mComposing.setLength(mComposing.length()-1); + } + } + + onKey(c, null); + + return true; + } + + /** + * Use this to monitor key events being delivered to the application. + * We get first crack at them, and can either resume them or let them + * continue to the app. + */ + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + // The InputMethodService already takes care of the back + // key for us, to dismiss the input method if it is shown. + // However, our keyboard could be showing a pop-up window + // that back should dismiss, so we first allow it to do that. + if (event.getRepeatCount() == 0 && mInputView != null) { + if (mInputView.handleBack()) { + return true; + } + } + break; + + case KeyEvent.KEYCODE_DEL: + // Special handling of the delete key: if we currently are + // composing text for the user, we want to modify that instead + // of let the application to the delete itself. + if (mComposing.length() > 0) { + onKey(Keyboard.KEYCODE_DELETE, null); + return true; + } + break; + + case KeyEvent.KEYCODE_ENTER: + // Let the underlying text editor always handle these. + return false; + + default: + // For all other keys, if we want to do transformations on + // text being entered with a hard keyboard, we need to process + // it and do the appropriate action. + if (PROCESS_HARD_KEYS) { + if (keyCode == KeyEvent.KEYCODE_SPACE + && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) { + // A silly example: in our input method, Alt+Space + // is a shortcut for 'android' in lower case. + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + // First, tell the editor that it is no longer in the + // shift state, since we are consuming this. + ic.clearMetaKeyStates(KeyEvent.META_ALT_ON); + keyDownUp(KeyEvent.KEYCODE_A); + keyDownUp(KeyEvent.KEYCODE_N); + keyDownUp(KeyEvent.KEYCODE_D); + keyDownUp(KeyEvent.KEYCODE_R); + keyDownUp(KeyEvent.KEYCODE_O); + keyDownUp(KeyEvent.KEYCODE_I); + keyDownUp(KeyEvent.KEYCODE_D); + // And we consume this event. + return true; + } + } + if (mPredictionOn && translateKeyDown(keyCode, event)) { + return true; + } + } + } + + return super.onKeyDown(keyCode, event); + } + + /** + * Use this to monitor key events being delivered to the application. + * We get first crack at them, and can either resume them or let them + * continue to the app. + */ + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { + // If we want to do transformations on text being entered with a hard + // keyboard, we need to process the up events to update the meta key + // state we are tracking. + if (PROCESS_HARD_KEYS) { + if (mPredictionOn) { + mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, + keyCode, event); + } + } + + return super.onKeyUp(keyCode, event); + } + + /** + * Helper function to commit any text being composed in to the editor. + */ + private void commitTyped(InputConnection inputConnection) { + if (mComposing.length() > 0) { + inputConnection.commitText(mComposing, mComposing.length()); + mComposing.setLength(0); + updateCandidates(); + } + } + + /** + * Helper to update the shift state of our keyboard based on the initial + * editor state. + */ + private void updateShiftKeyState(EditorInfo attr) { + if (attr != null + && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) { + int caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); + mInputView.setShifted(mCapsLock || caps != 0); + } + } + + /** + * Helper to determine if a given character code is alphabetic. + */ + private boolean isAlphabet(int code) { + if (Character.isLetter(code)) { + return true; + } else { + return false; + } + } + + /** + * Helper to send a key down / key up pair to the current editor. + */ + private void keyDownUp(int keyEventCode) { + getCurrentInputConnection().sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); + getCurrentInputConnection().sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); + } + + /** + * Helper to send a character to the editor as raw key events. + */ + private void sendKey(int keyCode) { + switch (keyCode) { + case '\n': + keyDownUp(KeyEvent.KEYCODE_ENTER); + break; + default: + if (keyCode >= '0' && keyCode <= '9') { + keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0); + } else { + getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1); + } + break; + } + } + + // Implementation of KeyboardViewListener + + public void onKey(int primaryCode, int[] keyCodes) { + if (isWordSeparator(primaryCode)) { + // Handle separator + if (mComposing.length() > 0) { + commitTyped(getCurrentInputConnection()); + } + sendKey(primaryCode); + updateShiftKeyState(getCurrentInputEditorInfo()); + } else if (primaryCode == Keyboard.KEYCODE_DELETE) { + handleBackspace(); + } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { + handleShift(); + } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { + handleClose(); + return; + } else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) { + // Show a menu or somethin' + } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE + && mInputView != null) { + Keyboard current = mInputView.getKeyboard(); + if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) { + current = mQwertyKeyboard; + } else { + current = mSymbolsKeyboard; + } + mInputView.setKeyboard(current); + if (current == mSymbolsKeyboard) { + current.setShifted(false); + } + } else { + handleCharacter(primaryCode, keyCodes); + } + } + + public void onText(CharSequence text) { + InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + ic.beginBatchEdit(); + if (mComposing.length() > 0) { + commitTyped(ic); + } + ic.commitText(text, 0); + ic.endBatchEdit(); + updateShiftKeyState(getCurrentInputEditorInfo()); + } + + /** + * Update the list of available candidates from the current composing + * text. This will need to be filled in by however you are determining + * candidates. + */ + private void updateCandidates() { + if (!mCompletionOn) { + if (mComposing.length() > 0) { + ArrayList<String> list = new ArrayList<String>(); + list.add(mComposing.toString()); + setSuggestions(list, true, true); + } else { + setSuggestions(null, false, false); + } + } + } + + public void setSuggestions(List<String> suggestions, boolean completions, + boolean typedWordValid) { + if (suggestions != null && suggestions.size() > 0) { + setCandidatesViewShown(true); + } else if (isFullscreenMode()) { + setCandidatesViewShown(true); + } + if (mCandidateView != null) { + mCandidateView.setSuggestions(suggestions, completions, typedWordValid); + } + } + + private void handleBackspace() { + final int length = mComposing.length(); + if (length > 1) { + mComposing.delete(length - 1, length); + getCurrentInputConnection().setComposingText(mComposing, mComposing.length()); + updateCandidates(); + } else if (length > 0) { + mComposing.setLength(0); + getCurrentInputConnection().commitText("", 0); + updateCandidates(); + } else { + keyDownUp(KeyEvent.KEYCODE_DEL); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + } + + private void handleShift() { + if (mInputView == null) { + return; + } + + Keyboard currentKeyboard = mInputView.getKeyboard(); + if (mQwertyKeyboard == currentKeyboard) { + // Alphabet keyboard + checkToggleCapsLock(); + mInputView.setShifted(mCapsLock || !mInputView.isShifted()); + } else if (currentKeyboard == mSymbolsKeyboard) { + mSymbolsKeyboard.setShifted(true); + mInputView.setKeyboard(mSymbolsShiftedKeyboard); + mSymbolsShiftedKeyboard.setShifted(true); + } else if (currentKeyboard == mSymbolsShiftedKeyboard) { + mSymbolsShiftedKeyboard.setShifted(false); + mInputView.setKeyboard(mSymbolsKeyboard); + mSymbolsKeyboard.setShifted(false); + } + } + + private void handleCharacter(int primaryCode, int[] keyCodes) { + if (isInputViewShown()) { + if (mInputView.isShifted()) { + primaryCode = Character.toUpperCase(primaryCode); + } + } + if (isAlphabet(primaryCode) && mPredictionOn) { + mComposing.append((char) primaryCode); + getCurrentInputConnection().setComposingText(mComposing, mComposing.length()); + updateShiftKeyState(getCurrentInputEditorInfo()); + updateCandidates(); + } else { + getCurrentInputConnection().commitText( + String.valueOf((char) primaryCode), 1); + } + } + + private void handleClose() { + commitTyped(getCurrentInputConnection()); + dismissSoftInput(0); + mInputView.closing(); + } + + private void checkToggleCapsLock() { + long now = System.currentTimeMillis(); + if (mLastShiftTime + 800 > now) { + mCapsLock = !mCapsLock; + mLastShiftTime = 0; + } else { + mLastShiftTime = now; + } + } + + private String getWordSeparators() { + return mWordSeparators; + } + + public boolean isWordSeparator(int code) { + String separators = getWordSeparators(); + return separators.contains(String.valueOf((char)code)); + } + + public void pickDefaultCandidate() { + pickSuggestionManually(0); + } + + public void pickSuggestionManually(int index) { + if (mCompletionOn && mCompletions != null && index >= 0 + && index < mCompletions.length) { + CompletionInfo ci = mCompletions[index]; + getCurrentInputConnection().commitCompletion(ci); + if (mCandidateView != null) { + mCandidateView.clear(); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + } else if (mComposing.length() > 0) { + // If we were generating candidate suggestions for the current + // text, we would commit one of them here. But for this sample, + // we will just commit the current text. + commitTyped(getCurrentInputConnection()); + } + } + + public void swipeRight() { + if (mCompletionOn) { + pickDefaultCandidate(); + } + } + + public void swipeLeft() { + handleBackspace(); + } + + public void swipeDown() { + handleClose(); + } + + public void swipeUp() { + } + + public void onPress(int primaryCode) { + } + + public void onRelease(int primaryCode) { + } +} |
