├── .gitignore ├── CustomKeyboard.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── blackcj │ │ └── customkeyboard │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ ├── android │ │ │ └── inputmethodcommon │ │ │ │ ├── InputMethodSettingsFragment.java │ │ │ │ ├── InputMethodSettingsImpl.java │ │ │ │ └── InputMethodSettingsInterface.java │ │ │ └── blackcj │ │ │ └── customkeyboard │ │ │ ├── CandidateView.java │ │ │ ├── ImePreferences.java │ │ │ ├── LatinKeyboard.java │ │ │ ├── LatinKeyboardView.java │ │ │ └── SoftKeyboard.java │ └── res │ │ ├── drawable-hdpi │ │ ├── icon_en_gb.png │ │ ├── icon_en_us.png │ │ ├── sym_keyboard_delete.png │ │ ├── sym_keyboard_done.png │ │ ├── sym_keyboard_language_switch.png │ │ ├── sym_keyboard_return.png │ │ ├── sym_keyboard_search.png │ │ ├── sym_keyboard_shift.png │ │ └── sym_keyboard_space.png │ │ ├── drawable │ │ ├── ic_close_black.xml │ │ ├── key_background.xml │ │ ├── normal.xml │ │ └── pressed.xml │ │ ├── layout │ │ ├── input.xml │ │ ├── keyboard_popup_layout.xml │ │ └── preview.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-land │ │ └── dimens.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── ime_preferences.xml │ │ ├── keyboard_popup_template.xml │ │ ├── method.xml │ │ ├── qwerty.xml │ │ ├── symbols.xml │ │ └── symbols_shift.xml │ └── test │ └── java │ └── com │ └── blackcj │ └── customkeyboard │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keyboard.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | -------------------------------------------------------------------------------- /CustomKeyboard.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidCustomKeyboard 2 | 3 | Android custom keyboard with predictive text. 4 | 5 | ![Animated Gif](keyboard.gif) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.blackcj.customkeyboard" 9 | minSdkVersion 19 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chris.black/Documents/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/blackcj/customkeyboard/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.blackcj.customkeyboard; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/inputmethodcommon/InputMethodSettingsFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This is a part of the inputmethod-common static Java library. 19 | * The original source code can be found at frameworks/opt/inputmethodcommon of Android Open Source 20 | * Project. 21 | */ 22 | 23 | package com.android.inputmethodcommon; 24 | 25 | import android.content.Context; 26 | import android.graphics.drawable.Drawable; 27 | import android.os.Bundle; 28 | import android.preference.PreferenceFragment; 29 | 30 | /** 31 | * This is a helper class for an IME's settings preference fragment. It's recommended for every 32 | * IME to have its own settings preference fragment which inherits this class. 33 | */ 34 | public abstract class InputMethodSettingsFragment extends PreferenceFragment 35 | implements InputMethodSettingsInterface { 36 | private final InputMethodSettingsImpl mSettings = new InputMethodSettingsImpl(); 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | final Context context = getActivity(); 41 | setPreferenceScreen(getPreferenceManager().createPreferenceScreen(context)); 42 | mSettings.init(context, getPreferenceScreen()); 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | @Override 49 | public void setInputMethodSettingsCategoryTitle(int resId) { 50 | mSettings.setInputMethodSettingsCategoryTitle(resId); 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | @Override 57 | public void setInputMethodSettingsCategoryTitle(CharSequence title) { 58 | mSettings.setInputMethodSettingsCategoryTitle(title); 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | @Override 65 | public void setSubtypeEnablerTitle(int resId) { 66 | mSettings.setSubtypeEnablerTitle(resId); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | @Override 73 | public void setSubtypeEnablerTitle(CharSequence title) { 74 | mSettings.setSubtypeEnablerTitle(title); 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | @Override 81 | public void setSubtypeEnablerIcon(int resId) { 82 | mSettings.setSubtypeEnablerIcon(resId); 83 | } 84 | 85 | /** 86 | * {@inheritDoc} 87 | */ 88 | @Override 89 | public void setSubtypeEnablerIcon(Drawable drawable) { 90 | mSettings.setSubtypeEnablerIcon(drawable); 91 | } 92 | 93 | /** 94 | * {@inheritDoc} 95 | */ 96 | @Override 97 | public void onResume() { 98 | super.onResume(); 99 | mSettings.updateSubtypeEnabler(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/inputmethodcommon/InputMethodSettingsImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This is a part of the inputmethod-common static Java library. 19 | * The original source code can be found at frameworks/opt/inputmethodcommon of Android Open Source 20 | * Project. 21 | */ 22 | 23 | package com.android.inputmethodcommon; 24 | 25 | import android.content.Context; 26 | import android.content.Intent; 27 | import android.graphics.drawable.Drawable; 28 | import android.preference.Preference; 29 | import android.preference.Preference.OnPreferenceClickListener; 30 | import android.preference.PreferenceScreen; 31 | import android.provider.Settings; 32 | import android.text.TextUtils; 33 | import android.view.inputmethod.InputMethodInfo; 34 | import android.view.inputmethod.InputMethodManager; 35 | import android.view.inputmethod.InputMethodSubtype; 36 | 37 | import java.util.List; 38 | 39 | /* package private */ class InputMethodSettingsImpl implements InputMethodSettingsInterface { 40 | private Preference mSubtypeEnablerPreference; 41 | private int mInputMethodSettingsCategoryTitleRes; 42 | private CharSequence mInputMethodSettingsCategoryTitle; 43 | private int mSubtypeEnablerTitleRes; 44 | private CharSequence mSubtypeEnablerTitle; 45 | private int mSubtypeEnablerIconRes; 46 | private Drawable mSubtypeEnablerIcon; 47 | private InputMethodManager mImm; 48 | private InputMethodInfo mImi; 49 | private Context mContext; 50 | 51 | /** 52 | * Initialize internal states of this object. 53 | * @param context the context for this application. 54 | * @param prefScreen a PreferenceScreen of PreferenceActivity or PreferenceFragment. 55 | * @return true if this application is an IME and has two or more subtypes, false otherwise. 56 | */ 57 | public boolean init(final Context context, final PreferenceScreen prefScreen) { 58 | mContext = context; 59 | mImm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 60 | mImi = getMyImi(context, mImm); 61 | if (mImi == null || mImi.getSubtypeCount() <= 1) { 62 | return false; 63 | } 64 | mSubtypeEnablerPreference = new Preference(context); 65 | mSubtypeEnablerPreference 66 | .setOnPreferenceClickListener(new OnPreferenceClickListener() { 67 | @Override 68 | public boolean onPreferenceClick(Preference preference) { 69 | final CharSequence title = getSubtypeEnablerTitle(context); 70 | final Intent intent = 71 | new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); 72 | intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, mImi.getId()); 73 | if (!TextUtils.isEmpty(title)) { 74 | intent.putExtra(Intent.EXTRA_TITLE, title); 75 | } 76 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 77 | | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 78 | | Intent.FLAG_ACTIVITY_CLEAR_TOP); 79 | context.startActivity(intent); 80 | return true; 81 | } 82 | }); 83 | prefScreen.addPreference(mSubtypeEnablerPreference); 84 | updateSubtypeEnabler(); 85 | return true; 86 | } 87 | 88 | private static InputMethodInfo getMyImi(Context context, InputMethodManager imm) { 89 | final List imis = imm.getInputMethodList(); 90 | for (int i = 0; i < imis.size(); ++i) { 91 | final InputMethodInfo imi = imis.get(i); 92 | if (imis.get(i).getPackageName().equals(context.getPackageName())) { 93 | return imi; 94 | } 95 | } 96 | return null; 97 | } 98 | 99 | private static String getEnabledSubtypesLabel( 100 | Context context, InputMethodManager imm, InputMethodInfo imi) { 101 | if (context == null || imm == null || imi == null) return null; 102 | final List subtypes = imm.getEnabledInputMethodSubtypeList(imi, true); 103 | final StringBuilder sb = new StringBuilder(); 104 | final int N = subtypes.size(); 105 | for (int i = 0; i < N; ++i) { 106 | final InputMethodSubtype subtype = subtypes.get(i); 107 | if (sb.length() > 0) { 108 | sb.append(", "); 109 | } 110 | sb.append(subtype.getDisplayName(context, imi.getPackageName(), 111 | imi.getServiceInfo().applicationInfo)); 112 | } 113 | return sb.toString(); 114 | } 115 | /** 116 | * {@inheritDoc} 117 | */ 118 | @Override 119 | public void setInputMethodSettingsCategoryTitle(int resId) { 120 | mInputMethodSettingsCategoryTitleRes = resId; 121 | updateSubtypeEnabler(); 122 | } 123 | 124 | /** 125 | * {@inheritDoc} 126 | */ 127 | @Override 128 | public void setInputMethodSettingsCategoryTitle(CharSequence title) { 129 | mInputMethodSettingsCategoryTitleRes = 0; 130 | mInputMethodSettingsCategoryTitle = title; 131 | updateSubtypeEnabler(); 132 | } 133 | 134 | /** 135 | * {@inheritDoc} 136 | */ 137 | @Override 138 | public void setSubtypeEnablerTitle(int resId) { 139 | mSubtypeEnablerTitleRes = resId; 140 | updateSubtypeEnabler(); 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | @Override 147 | public void setSubtypeEnablerTitle(CharSequence title) { 148 | mSubtypeEnablerTitleRes = 0; 149 | mSubtypeEnablerTitle = title; 150 | updateSubtypeEnabler(); 151 | } 152 | 153 | /** 154 | * {@inheritDoc} 155 | */ 156 | @Override 157 | public void setSubtypeEnablerIcon(int resId) { 158 | mSubtypeEnablerIconRes = resId; 159 | updateSubtypeEnabler(); 160 | } 161 | 162 | /** 163 | * {@inheritDoc} 164 | */ 165 | @Override 166 | public void setSubtypeEnablerIcon(Drawable drawable) { 167 | mSubtypeEnablerIconRes = 0; 168 | mSubtypeEnablerIcon = drawable; 169 | updateSubtypeEnabler(); 170 | } 171 | 172 | private CharSequence getSubtypeEnablerTitle(Context context) { 173 | if (mSubtypeEnablerTitleRes != 0) { 174 | return context.getString(mSubtypeEnablerTitleRes); 175 | } else { 176 | return mSubtypeEnablerTitle; 177 | } 178 | } 179 | 180 | public void updateSubtypeEnabler() { 181 | if (mSubtypeEnablerPreference != null) { 182 | if (mSubtypeEnablerTitleRes != 0) { 183 | mSubtypeEnablerPreference.setTitle(mSubtypeEnablerTitleRes); 184 | } else if (!TextUtils.isEmpty(mSubtypeEnablerTitle)) { 185 | mSubtypeEnablerPreference.setTitle(mSubtypeEnablerTitle); 186 | } 187 | final String summary = getEnabledSubtypesLabel(mContext, mImm, mImi); 188 | if (!TextUtils.isEmpty(summary)) { 189 | mSubtypeEnablerPreference.setSummary(summary); 190 | } 191 | if (mSubtypeEnablerIconRes != 0) { 192 | mSubtypeEnablerPreference.setIcon(mSubtypeEnablerIconRes); 193 | } else if (mSubtypeEnablerIcon != null) { 194 | mSubtypeEnablerPreference.setIcon(mSubtypeEnablerIcon); 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/inputmethodcommon/InputMethodSettingsInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | 17 | /** 18 | * This is a part of the inputmethod-common static Java library. 19 | * The original source code can be found at frameworks/opt/inputmethodcommon of Android Open Source 20 | * Project. 21 | */ 22 | 23 | package com.android.inputmethodcommon; 24 | 25 | import android.graphics.drawable.Drawable; 26 | 27 | /** 28 | * InputMethodSettingsInterface is the interface for adding IME related preferences to 29 | * PreferenceActivity or PreferenceFragment. 30 | */ 31 | public interface InputMethodSettingsInterface { 32 | /** 33 | * Sets the title for the input method settings category with a resource ID. 34 | * @param resId The resource ID of the title. 35 | */ 36 | public void setInputMethodSettingsCategoryTitle(int resId); 37 | 38 | /** 39 | * Sets the title for the input method settings category with a CharSequence. 40 | * @param title The title for this preference. 41 | */ 42 | public void setInputMethodSettingsCategoryTitle(CharSequence title); 43 | 44 | /** 45 | * Sets the title for the input method enabler preference for launching subtype enabler with a 46 | * resource ID. 47 | * @param resId The resource ID of the title. 48 | */ 49 | public void setSubtypeEnablerTitle(int resId); 50 | 51 | /** 52 | * Sets the title for the input method enabler preference for launching subtype enabler with a 53 | * CharSequence. 54 | * @param title The title for this preference. 55 | */ 56 | public void setSubtypeEnablerTitle(CharSequence title); 57 | 58 | /** 59 | * Sets the icon for the preference for launching subtype enabler with a resource ID. 60 | * @param resId The resource id of an optional icon for the preference. 61 | */ 62 | public void setSubtypeEnablerIcon(int resId); 63 | 64 | /** 65 | * Sets the icon for the Preference for launching subtype enabler with a Drawable. 66 | * @param drawable The drawable of an optional icon for the preference. 67 | */ 68 | public void setSubtypeEnablerIcon(Drawable drawable); 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/blackcj/customkeyboard/CandidateView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2009 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.blackcj.customkeyboard; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.graphics.Canvas; 22 | import android.graphics.Paint; 23 | import android.graphics.Rect; 24 | import android.graphics.drawable.Drawable; 25 | import android.view.GestureDetector; 26 | import android.view.MotionEvent; 27 | import android.view.View; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | public class CandidateView extends View { 33 | 34 | private static final int OUT_OF_BOUNDS = -1; 35 | 36 | private SoftKeyboard mService; 37 | private List mSuggestions; 38 | private int mSelectedIndex; 39 | private int mTouchX = OUT_OF_BOUNDS; 40 | private Drawable mSelectionHighlight; 41 | private boolean mTypedWordValid; 42 | 43 | private Rect mBgPadding; 44 | 45 | private static final int MAX_SUGGESTIONS = 32; 46 | private static final int SCROLL_PIXELS = 20; 47 | 48 | private int[] mWordWidth = new int[MAX_SUGGESTIONS]; 49 | private int[] mWordX = new int[MAX_SUGGESTIONS]; 50 | 51 | private static final int X_GAP = 60; 52 | 53 | private static final List EMPTY_LIST = new ArrayList(); 54 | 55 | private int mColorNormal; 56 | private int mColorRecommended; 57 | private int mColorOther; 58 | private int mVerticalPadding; 59 | private Paint mPaint; 60 | private boolean mScrolled; 61 | private int mTargetScrollX; 62 | 63 | private int mTotalWidth; 64 | 65 | private GestureDetector mGestureDetector; 66 | 67 | /** 68 | * Construct a CandidateView for showing suggested words for completion. 69 | * @param context 70 | * @param attrs 71 | */ 72 | public CandidateView(Context context) { 73 | super(context); 74 | mSelectionHighlight = context.getResources().getDrawable( 75 | android.R.drawable.list_selector_background); 76 | mSelectionHighlight.setState(new int[] { 77 | android.R.attr.state_enabled, 78 | android.R.attr.state_focused, 79 | android.R.attr.state_window_focused, 80 | android.R.attr.state_pressed 81 | }); 82 | 83 | Resources r = context.getResources(); 84 | 85 | setBackgroundColor(r.getColor(R.color.candidate_background)); 86 | 87 | mColorNormal = r.getColor(R.color.candidate_normal); 88 | mColorRecommended = r.getColor(R.color.candidate_recommended); 89 | mColorOther = r.getColor(R.color.candidate_other); 90 | mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding); 91 | 92 | mPaint = new Paint(); 93 | mPaint.setColor(mColorNormal); 94 | mPaint.setAntiAlias(true); 95 | mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height)); 96 | mPaint.setStrokeWidth(0); 97 | 98 | mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { 99 | @Override 100 | public boolean onScroll(MotionEvent e1, MotionEvent e2, 101 | float distanceX, float distanceY) { 102 | mScrolled = true; 103 | int sx = getScrollX(); 104 | sx += distanceX; 105 | if (sx < 0) { 106 | sx = 0; 107 | } 108 | if (sx + getWidth() > mTotalWidth) { 109 | sx -= distanceX; 110 | } 111 | mTargetScrollX = sx; 112 | scrollTo(sx, getScrollY()); 113 | invalidate(); 114 | return true; 115 | } 116 | }); 117 | setHorizontalFadingEdgeEnabled(true); 118 | setWillNotDraw(false); 119 | setHorizontalScrollBarEnabled(false); 120 | setVerticalScrollBarEnabled(false); 121 | } 122 | 123 | /** 124 | * A connection back to the service to communicate with the text field 125 | * @param listener 126 | */ 127 | public void setService(SoftKeyboard listener) { 128 | mService = listener; 129 | } 130 | 131 | @Override 132 | public int computeHorizontalScrollRange() { 133 | return mTotalWidth; 134 | } 135 | 136 | @Override 137 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 138 | int measuredWidth = resolveSize(50, widthMeasureSpec); 139 | 140 | // Get the desired height of the icon menu view (last row of items does 141 | // not have a divider below) 142 | Rect padding = new Rect(); 143 | mSelectionHighlight.getPadding(padding); 144 | final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding 145 | + padding.top + padding.bottom; 146 | 147 | // Maximum possible width and desired height 148 | setMeasuredDimension(measuredWidth, 149 | resolveSize(desiredHeight, heightMeasureSpec)); 150 | } 151 | 152 | /** 153 | * If the canvas is null, then only touch calculations are performed to pick the target 154 | * candidate. 155 | */ 156 | @Override 157 | protected void onDraw(Canvas canvas) { 158 | if (canvas != null) { 159 | super.onDraw(canvas); 160 | } 161 | mTotalWidth = 0; 162 | if (mSuggestions == null) return; 163 | 164 | if (mBgPadding == null) { 165 | mBgPadding = new Rect(0, 0, 0, 0); 166 | if (getBackground() != null) { 167 | getBackground().getPadding(mBgPadding); 168 | } 169 | } 170 | int x = 0; 171 | final int count = mSuggestions.size(); 172 | final int height = getHeight(); 173 | final Rect bgPadding = mBgPadding; 174 | final Paint paint = mPaint; 175 | final int touchX = mTouchX; 176 | final int scrollX = getScrollX(); 177 | final boolean scrolled = mScrolled; 178 | final boolean typedWordValid = mTypedWordValid; 179 | final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent()); 180 | 181 | for (int i = 0; i < count; i++) { 182 | String suggestion = mSuggestions.get(i); 183 | float textWidth = paint.measureText(suggestion); 184 | final int wordWidth = (int) textWidth + X_GAP * 2; 185 | 186 | mWordX[i] = x; 187 | mWordWidth[i] = wordWidth; 188 | paint.setColor(mColorNormal); 189 | if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { 190 | if (canvas != null) { 191 | canvas.translate(x, 0); 192 | mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); 193 | mSelectionHighlight.draw(canvas); 194 | canvas.translate(-x, 0); 195 | } 196 | mSelectedIndex = i; 197 | } 198 | 199 | if (canvas != null) { 200 | if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) { 201 | paint.setFakeBoldText(true); 202 | paint.setColor(mColorRecommended); 203 | } else if (i != 0) { 204 | paint.setColor(mColorOther); 205 | } 206 | canvas.drawText(suggestion, x + X_GAP, y, paint); 207 | paint.setColor(mColorOther); 208 | canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, 209 | x + wordWidth + 0.5f, height + 1, paint); 210 | paint.setFakeBoldText(false); 211 | } 212 | x += wordWidth; 213 | } 214 | mTotalWidth = x; 215 | if (mTargetScrollX != getScrollX()) { 216 | scrollToTarget(); 217 | } 218 | } 219 | 220 | private void scrollToTarget() { 221 | int sx = getScrollX(); 222 | if (mTargetScrollX > sx) { 223 | sx += SCROLL_PIXELS; 224 | if (sx >= mTargetScrollX) { 225 | sx = mTargetScrollX; 226 | requestLayout(); 227 | } 228 | } else { 229 | sx -= SCROLL_PIXELS; 230 | if (sx <= mTargetScrollX) { 231 | sx = mTargetScrollX; 232 | requestLayout(); 233 | } 234 | } 235 | scrollTo(sx, getScrollY()); 236 | invalidate(); 237 | } 238 | 239 | public void setSuggestions(List suggestions, boolean completions, 240 | boolean typedWordValid) { 241 | clear(); 242 | if (suggestions != null) { 243 | mSuggestions = new ArrayList(suggestions); 244 | } 245 | mTypedWordValid = typedWordValid; 246 | scrollTo(0, 0); 247 | mTargetScrollX = 0; 248 | // Compute the total width 249 | onDraw(null); 250 | invalidate(); 251 | requestLayout(); 252 | } 253 | 254 | public void clear() { 255 | mSuggestions = EMPTY_LIST; 256 | mTouchX = OUT_OF_BOUNDS; 257 | mSelectedIndex = -1; 258 | invalidate(); 259 | } 260 | 261 | @Override 262 | public boolean onTouchEvent(MotionEvent me) { 263 | 264 | if (mGestureDetector.onTouchEvent(me)) { 265 | return true; 266 | } 267 | 268 | int action = me.getAction(); 269 | int x = (int) me.getX(); 270 | int y = (int) me.getY(); 271 | mTouchX = x; 272 | 273 | switch (action) { 274 | case MotionEvent.ACTION_DOWN: 275 | mScrolled = false; 276 | invalidate(); 277 | break; 278 | case MotionEvent.ACTION_MOVE: 279 | if (y <= 0) { 280 | // Fling up!? 281 | if (mSelectedIndex >= 0) { 282 | mService.pickSuggestionManually(mSelectedIndex); 283 | mSelectedIndex = -1; 284 | } 285 | } 286 | invalidate(); 287 | break; 288 | case MotionEvent.ACTION_UP: 289 | if (!mScrolled) { 290 | if (mSelectedIndex >= 0) { 291 | mService.pickSuggestionManually(mSelectedIndex); 292 | } 293 | } 294 | mSelectedIndex = -1; 295 | removeHighlight(); 296 | requestLayout(); 297 | break; 298 | } 299 | return true; 300 | } 301 | 302 | /** 303 | * For flick through from keyboard, call this method with the x coordinate of the flick 304 | * gesture. 305 | * @param x 306 | */ 307 | public void takeSuggestionAt(float x) { 308 | mTouchX = (int) x; 309 | // To detect candidate 310 | onDraw(null); 311 | if (mSelectedIndex >= 0) { 312 | mService.pickSuggestionManually(mSelectedIndex); 313 | } 314 | invalidate(); 315 | } 316 | 317 | private void removeHighlight() { 318 | mTouchX = OUT_OF_BOUNDS; 319 | invalidate(); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /app/src/main/java/com/blackcj/customkeyboard/ImePreferences.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.blackcj.customkeyboard; 18 | 19 | import android.content.Intent; 20 | import android.os.Bundle; 21 | import android.preference.PreferenceActivity; 22 | 23 | import com.android.inputmethodcommon.InputMethodSettingsFragment; 24 | 25 | /** 26 | * Displays the IME preferences inside the input method setting. 27 | */ 28 | public class ImePreferences extends PreferenceActivity { 29 | @Override 30 | public Intent getIntent() { 31 | final Intent modIntent = new Intent(super.getIntent()); 32 | modIntent.putExtra(EXTRA_SHOW_FRAGMENT, Settings.class.getName()); 33 | modIntent.putExtra(EXTRA_NO_HEADERS, true); 34 | return modIntent; 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | 41 | // We overwrite the title of the activity, as the default one is "Voice Search". 42 | setTitle(R.string.settings_name); 43 | } 44 | 45 | @Override 46 | protected boolean isValidFragment(final String fragmentName) { 47 | return Settings.class.getName().equals(fragmentName); 48 | } 49 | 50 | public static class Settings extends InputMethodSettingsFragment { 51 | @Override 52 | public void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | setInputMethodSettingsCategoryTitle(R.string.language_selection_title); 55 | setSubtypeEnablerTitle(R.string.select_language); 56 | 57 | // Load the preferences from an XML resource 58 | addPreferencesFromResource(R.xml.ime_preferences); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/blackcj/customkeyboard/LatinKeyboard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2009 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.blackcj.customkeyboard; 18 | 19 | import android.content.Context; 20 | import android.content.res.Resources; 21 | import android.content.res.XmlResourceParser; 22 | import android.graphics.drawable.Drawable; 23 | import android.inputmethodservice.Keyboard; 24 | import android.view.inputmethod.EditorInfo; 25 | import android.view.inputmethod.InputMethodManager; 26 | 27 | public class LatinKeyboard extends Keyboard { 28 | 29 | private Key mEnterKey; 30 | private Key mSpaceKey; 31 | /** 32 | * Stores the current state of the mode change key. Its width will be dynamically updated to 33 | * match the region of {@link #mModeChangeKey} when {@link #mModeChangeKey} becomes invisible. 34 | */ 35 | private Key mModeChangeKey; 36 | /** 37 | * Stores the current state of the language switch key (a.k.a. globe key). This should be 38 | * visible while {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod(IBinder)} 39 | * returns true. When this key becomes invisible, its width will be shrunk to zero. 40 | */ 41 | private Key mLanguageSwitchKey; 42 | /** 43 | * Stores the size and other information of {@link #mModeChangeKey} when 44 | * {@link #mLanguageSwitchKey} is visible. This should be immutable and will be used only as a 45 | * reference size when the visibility of {@link #mLanguageSwitchKey} is changed. 46 | */ 47 | private Key mSavedModeChangeKey; 48 | /** 49 | * Stores the size and other information of {@link #mLanguageSwitchKey} when it is visible. 50 | * This should be immutable and will be used only as a reference size when the visibility of 51 | * {@link #mLanguageSwitchKey} is changed. 52 | */ 53 | private Key mSavedLanguageSwitchKey; 54 | 55 | public LatinKeyboard(Context context, int xmlLayoutResId) { 56 | super(context, xmlLayoutResId); 57 | } 58 | 59 | public LatinKeyboard(Context context, int layoutTemplateResId, 60 | CharSequence characters, int columns, int horizontalPadding) { 61 | super(context, layoutTemplateResId, characters, columns, horizontalPadding); 62 | } 63 | 64 | @Override 65 | protected Key createKeyFromXml(Resources res, Row parent, int x, int y, 66 | XmlResourceParser parser) { 67 | Key key = new LatinKey(res, parent, x, y, parser); 68 | if (key.codes[0] == 10) { 69 | mEnterKey = key; 70 | } else if (key.codes[0] == ' ') { 71 | mSpaceKey = key; 72 | } else if (key.codes[0] == Keyboard.KEYCODE_MODE_CHANGE) { 73 | mModeChangeKey = key; 74 | mSavedModeChangeKey = new LatinKey(res, parent, x, y, parser); 75 | } else if (key.codes[0] == LatinKeyboardView.KEYCODE_LANGUAGE_SWITCH) { 76 | mLanguageSwitchKey = key; 77 | mSavedLanguageSwitchKey = new LatinKey(res, parent, x, y, parser); 78 | } 79 | return key; 80 | } 81 | 82 | /** 83 | * Dynamically change the visibility of the language switch key (a.k.a. globe key). 84 | * @param visible True if the language switch key should be visible. 85 | */ 86 | void setLanguageSwitchKeyVisibility(boolean visible) { 87 | if (visible) { 88 | // The language switch key should be visible. Restore the size of the mode change key 89 | // and language switch key using the saved layout. 90 | mModeChangeKey.width = mSavedModeChangeKey.width; 91 | mModeChangeKey.x = mSavedModeChangeKey.x; 92 | mLanguageSwitchKey.width = mSavedLanguageSwitchKey.width; 93 | mLanguageSwitchKey.icon = mSavedLanguageSwitchKey.icon; 94 | mLanguageSwitchKey.iconPreview = mSavedLanguageSwitchKey.iconPreview; 95 | } else { 96 | // The language switch key should be hidden. Change the width of the mode change key 97 | // to fill the space of the language key so that the user will not see any strange gap. 98 | mModeChangeKey.width = mSavedModeChangeKey.width + mSavedLanguageSwitchKey.width; 99 | mLanguageSwitchKey.width = 0; 100 | mLanguageSwitchKey.icon = null; 101 | mLanguageSwitchKey.iconPreview = null; 102 | } 103 | } 104 | 105 | /** 106 | * This looks at the ime options given by the current editor, to set the 107 | * appropriate label on the keyboard's enter key (if it has one). 108 | */ 109 | void setImeOptions(Resources res, int options) { 110 | if (mEnterKey == null) { 111 | return; 112 | } 113 | 114 | switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { 115 | case EditorInfo.IME_ACTION_GO: 116 | mEnterKey.iconPreview = null; 117 | mEnterKey.icon = null; 118 | mEnterKey.label = res.getText(R.string.label_go_key); 119 | break; 120 | case EditorInfo.IME_ACTION_NEXT: 121 | mEnterKey.iconPreview = null; 122 | mEnterKey.icon = null; 123 | mEnterKey.label = res.getText(R.string.label_next_key); 124 | break; 125 | case EditorInfo.IME_ACTION_SEARCH: 126 | mEnterKey.icon = res.getDrawable(R.drawable.sym_keyboard_search); 127 | mEnterKey.label = null; 128 | break; 129 | case EditorInfo.IME_ACTION_SEND: 130 | mEnterKey.iconPreview = null; 131 | mEnterKey.icon = null; 132 | mEnterKey.label = res.getText(R.string.label_send_key); 133 | break; 134 | default: 135 | mEnterKey.icon = res.getDrawable(R.drawable.sym_keyboard_return); 136 | mEnterKey.label = null; 137 | break; 138 | } 139 | } 140 | 141 | void setSpaceIcon(final Drawable icon) { 142 | if (mSpaceKey != null) { 143 | mSpaceKey.icon = icon; 144 | } 145 | } 146 | 147 | static class LatinKey extends Keyboard.Key { 148 | 149 | public LatinKey(Resources res, Keyboard.Row parent, int x, int y, 150 | XmlResourceParser parser) { 151 | super(res, parent, x, y, parser); 152 | } 153 | 154 | /** 155 | * Overriding this method so that we can reduce the target area for the key that 156 | * closes the keyboard. 157 | */ 158 | @Override 159 | public boolean isInside(int x, int y) { 160 | return super.isInside(x, codes[0] == KEYCODE_CANCEL ? y - 10 : y); 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/blackcj/customkeyboard/LatinKeyboardView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2009 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.blackcj.customkeyboard; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.Paint; 23 | import android.inputmethodservice.Keyboard; 24 | import android.inputmethodservice.Keyboard.Key; 25 | import android.inputmethodservice.KeyboardView; 26 | import android.util.AttributeSet; 27 | import android.util.Log; 28 | import android.view.inputmethod.InputMethodSubtype; 29 | 30 | import java.util.List; 31 | 32 | public class LatinKeyboardView extends KeyboardView { 33 | 34 | static final int KEYCODE_OPTIONS = -100; 35 | // TODO: Move this into android.inputmethodservice.Keyboard 36 | static final int KEYCODE_LANGUAGE_SWITCH = -101; 37 | 38 | public LatinKeyboardView(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | } 41 | 42 | public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { 43 | super(context, attrs, defStyle); 44 | } 45 | 46 | @Override 47 | protected boolean onLongPress(Key key) { 48 | if (key.codes[0] == Keyboard.KEYCODE_CANCEL) { 49 | getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); 50 | return true; 51 | /*} else if (key.codes[0] == 113) { 52 | 53 | return true; */ 54 | } else { 55 | //Log.d("LatinKeyboardView", "KEY: " + key.codes[0]); 56 | return super.onLongPress(key); 57 | } 58 | } 59 | 60 | void setSubtypeOnSpaceKey(final InputMethodSubtype subtype) { 61 | final LatinKeyboard keyboard = (LatinKeyboard)getKeyboard(); 62 | //keyboard.setSpaceIcon(getResources().getDrawable(subtype.getIconResId())); 63 | invalidateAllKeys(); 64 | } 65 | 66 | @Override 67 | public void onDraw(Canvas canvas) { 68 | super.onDraw(canvas); 69 | 70 | Paint paint = new Paint(); 71 | paint.setTextAlign(Paint.Align.CENTER); 72 | paint.setTextSize(28); 73 | paint.setColor(Color.LTGRAY); 74 | 75 | List keys = getKeyboard().getKeys(); 76 | for(Key key: keys) { 77 | if(key.label != null) { 78 | if (key.label.equals("q")) { 79 | canvas.drawText("1", key.x + (key.width - 25), key.y + 40, paint); 80 | } else if (key.label.equals("w")) { 81 | canvas.drawText("2", key.x + (key.width - 25), key.y + 40, paint); 82 | } else if (key.label.equals("e")) { 83 | canvas.drawText("3", key.x + (key.width - 25), key.y + 40, paint); 84 | } else if (key.label.equals("r")) { 85 | canvas.drawText("4", key.x + (key.width - 25), key.y + 40, paint); 86 | } else if (key.label.equals("t")) { 87 | canvas.drawText("5", key.x + (key.width - 25), key.y + 40, paint); 88 | } 89 | } 90 | 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/blackcj/customkeyboard/SoftKeyboard.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008-2009 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.blackcj.customkeyboard; 18 | 19 | import android.app.Dialog; 20 | import android.content.Context; 21 | import android.inputmethodservice.InputMethodService; 22 | import android.inputmethodservice.Keyboard; 23 | import android.inputmethodservice.KeyboardView; 24 | import android.os.IBinder; 25 | import android.text.InputType; 26 | import android.text.method.MetaKeyKeyListener; 27 | import android.util.Log; 28 | import android.view.KeyCharacterMap; 29 | import android.view.KeyEvent; 30 | import android.view.View; 31 | import android.view.Window; 32 | import android.view.inputmethod.CompletionInfo; 33 | import android.view.inputmethod.EditorInfo; 34 | import android.view.inputmethod.InputConnection; 35 | import android.view.inputmethod.InputMethodManager; 36 | import android.view.inputmethod.InputMethodSubtype; 37 | import android.view.textservice.SentenceSuggestionsInfo; 38 | import android.view.textservice.SpellCheckerSession; 39 | import android.view.textservice.SuggestionsInfo; 40 | import android.view.textservice.TextInfo; 41 | import android.view.textservice.TextServicesManager; 42 | 43 | import java.util.ArrayList; 44 | import java.util.List; 45 | 46 | /** 47 | * Example of writing an input method for a soft keyboard. This code is 48 | * focused on simplicity over completeness, so it should in no way be considered 49 | * to be a complete soft keyboard implementation. Its purpose is to provide 50 | * a basic example for how you would get started writing an input method, to 51 | * be fleshed out as appropriate. 52 | */ 53 | public class SoftKeyboard extends InputMethodService 54 | implements KeyboardView.OnKeyboardActionListener, SpellCheckerSession.SpellCheckerSessionListener { 55 | static final boolean DEBUG = false; 56 | 57 | /** 58 | * This boolean indicates the optional example code for performing 59 | * processing of hard keys in addition to regular text generation 60 | * from on-screen interaction. It would be used for input methods that 61 | * perform language translations (such as converting text entered on 62 | * a QWERTY keyboard to Chinese), but may not be used for input methods 63 | * that are primarily intended to be used for on-screen text entry. 64 | */ 65 | static final boolean PROCESS_HARD_KEYS = true; 66 | 67 | private InputMethodManager mInputMethodManager; 68 | 69 | private LatinKeyboardView mInputView; 70 | private CandidateView mCandidateView; 71 | private CompletionInfo[] mCompletions; 72 | 73 | private StringBuilder mComposing = new StringBuilder(); 74 | private boolean mPredictionOn; 75 | private boolean mCompletionOn; 76 | private int mLastDisplayWidth; 77 | private boolean mCapsLock; 78 | private long mLastShiftTime; 79 | private long mMetaState; 80 | 81 | private LatinKeyboard mSymbolsKeyboard; 82 | private LatinKeyboard mSymbolsShiftedKeyboard; 83 | private LatinKeyboard mQwertyKeyboard; 84 | 85 | private LatinKeyboard mCurKeyboard; 86 | 87 | private String mWordSeparators; 88 | 89 | private SpellCheckerSession mScs; 90 | private List mSuggestions; 91 | 92 | 93 | 94 | /** 95 | * Main initialization of the input method component. Be sure to call 96 | * to super class. 97 | */ 98 | @Override public void onCreate() { 99 | super.onCreate(); 100 | mInputMethodManager = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); 101 | mWordSeparators = getResources().getString(R.string.word_separators); 102 | final TextServicesManager tsm = (TextServicesManager) getSystemService( 103 | Context.TEXT_SERVICES_MANAGER_SERVICE); 104 | mScs = tsm.newSpellCheckerSession(null, null, this, true); 105 | } 106 | 107 | /** 108 | * This is the point where you can do all of your UI initialization. It 109 | * is called after creation and any configuration change. 110 | */ 111 | @Override public void onInitializeInterface() { 112 | if (mQwertyKeyboard != null) { 113 | // Configuration changes can happen after the keyboard gets recreated, 114 | // so we need to be able to re-build the keyboards if the available 115 | // space has changed. 116 | int displayWidth = getMaxWidth(); 117 | if (displayWidth == mLastDisplayWidth) return; 118 | mLastDisplayWidth = displayWidth; 119 | } 120 | mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty); 121 | mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols); 122 | mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift); 123 | } 124 | 125 | /** 126 | * Called by the framework when your view for creating input needs to 127 | * be generated. This will be called the first time your input method 128 | * is displayed, and every time it needs to be re-created such as due to 129 | * a configuration change. 130 | */ 131 | @Override public View onCreateInputView() { 132 | mInputView = (LatinKeyboardView) getLayoutInflater().inflate( 133 | R.layout.input, null); 134 | mInputView.setOnKeyboardActionListener(this); 135 | mInputView.setPreviewEnabled(false); 136 | setLatinKeyboard(mQwertyKeyboard); 137 | return mInputView; 138 | } 139 | 140 | private void setLatinKeyboard(LatinKeyboard nextKeyboard) { 141 | final boolean shouldSupportLanguageSwitchKey = 142 | mInputMethodManager.shouldOfferSwitchingToNextInputMethod(getToken()); 143 | nextKeyboard.setLanguageSwitchKeyVisibility(shouldSupportLanguageSwitchKey); 144 | mInputView.setKeyboard(nextKeyboard); 145 | } 146 | 147 | /** 148 | * Called by the framework when your view for showing candidates needs to 149 | * be generated, like {@link #onCreateInputView}. 150 | */ 151 | @Override public View onCreateCandidatesView() { 152 | mCandidateView = new CandidateView(this); 153 | mCandidateView.setService(this); 154 | return mCandidateView; 155 | } 156 | 157 | /** 158 | * This is the main point where we do our initialization of the input method 159 | * to begin operating on an application. At this point we have been 160 | * bound to the client, and are now receiving all of the detailed information 161 | * about the target of our edits. 162 | */ 163 | @Override public void onStartInput(EditorInfo attribute, boolean restarting) { 164 | super.onStartInput(attribute, restarting); 165 | 166 | // Reset our state. We want to do this even if restarting, because 167 | // the underlying state of the text editor could have changed in any way. 168 | mComposing.setLength(0); 169 | updateCandidates(); 170 | 171 | if (!restarting) { 172 | // Clear shift states. 173 | mMetaState = 0; 174 | } 175 | 176 | mPredictionOn = false; 177 | mCompletionOn = false; 178 | mCompletions = null; 179 | 180 | // We are now going to initialize our state based on the type of 181 | // text being edited. 182 | switch (attribute.inputType & InputType.TYPE_MASK_CLASS) { 183 | case InputType.TYPE_CLASS_NUMBER: 184 | case InputType.TYPE_CLASS_DATETIME: 185 | // Numbers and dates default to the symbols keyboard, with 186 | // no extra features. 187 | mCurKeyboard = mSymbolsKeyboard; 188 | break; 189 | 190 | case InputType.TYPE_CLASS_PHONE: 191 | // Phones will also default to the symbols keyboard, though 192 | // often you will want to have a dedicated phone keyboard. 193 | mCurKeyboard = mSymbolsKeyboard; 194 | break; 195 | 196 | case InputType.TYPE_CLASS_TEXT: 197 | // This is general text editing. We will default to the 198 | // normal alphabetic keyboard, and assume that we should 199 | // be doing predictive text (showing candidates as the 200 | // user types). 201 | mCurKeyboard = mQwertyKeyboard; 202 | mPredictionOn = true; 203 | 204 | // We now look for a few special variations of text that will 205 | // modify our behavior. 206 | int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION; 207 | if (variation == InputType.TYPE_TEXT_VARIATION_PASSWORD || 208 | variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { 209 | // Do not display predictions / what the user is typing 210 | // when they are entering a password. 211 | mPredictionOn = false; 212 | } 213 | 214 | if (variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 215 | || variation == InputType.TYPE_TEXT_VARIATION_URI 216 | || variation == InputType.TYPE_TEXT_VARIATION_FILTER) { 217 | // Our predictions are not useful for e-mail addresses 218 | // or URIs. 219 | mPredictionOn = false; 220 | } 221 | 222 | if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { 223 | // If this is an auto-complete text view, then our predictions 224 | // will not be shown and instead we will allow the editor 225 | // to supply their own. We only show the editor's 226 | // candidates when in fullscreen mode, otherwise relying 227 | // own it displaying its own UI. 228 | mPredictionOn = false; 229 | mCompletionOn = isFullscreenMode(); 230 | } 231 | 232 | // We also want to look at the current state of the editor 233 | // to decide whether our alphabetic keyboard should start out 234 | // shifted. 235 | updateShiftKeyState(attribute); 236 | break; 237 | 238 | default: 239 | // For all unknown input types, default to the alphabetic 240 | // keyboard with no special features. 241 | mCurKeyboard = mQwertyKeyboard; 242 | updateShiftKeyState(attribute); 243 | } 244 | 245 | // Update the label on the enter key, depending on what the application 246 | // says it will do. 247 | mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions); 248 | } 249 | 250 | /** 251 | * This is called when the user is done editing a field. We can use 252 | * this to reset our state. 253 | */ 254 | @Override public void onFinishInput() { 255 | super.onFinishInput(); 256 | 257 | // Clear current composing text and candidates. 258 | mComposing.setLength(0); 259 | updateCandidates(); 260 | 261 | // We only hide the candidates window when finishing input on 262 | // a particular editor, to avoid popping the underlying application 263 | // up and down if the user is entering text into the bottom of 264 | // its window. 265 | setCandidatesViewShown(false); 266 | 267 | mCurKeyboard = mQwertyKeyboard; 268 | if (mInputView != null) { 269 | mInputView.closing(); 270 | } 271 | } 272 | 273 | @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { 274 | super.onStartInputView(attribute, restarting); 275 | // Apply the selected keyboard to the input view. 276 | setLatinKeyboard(mCurKeyboard); 277 | mInputView.closing(); 278 | final InputMethodSubtype subtype = mInputMethodManager.getCurrentInputMethodSubtype(); 279 | mInputView.setSubtypeOnSpaceKey(subtype); 280 | } 281 | 282 | @Override 283 | public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { 284 | mInputView.setSubtypeOnSpaceKey(subtype); 285 | } 286 | 287 | /** 288 | * Deal with the editor reporting movement of its cursor. 289 | */ 290 | @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, 291 | int newSelStart, int newSelEnd, 292 | int candidatesStart, int candidatesEnd) { 293 | super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, 294 | candidatesStart, candidatesEnd); 295 | 296 | // If the current selection in the text view changes, we should 297 | // clear whatever candidate text we have. 298 | if (mComposing.length() > 0 && (newSelStart != candidatesEnd 299 | || newSelEnd != candidatesEnd)) { 300 | mComposing.setLength(0); 301 | updateCandidates(); 302 | InputConnection ic = getCurrentInputConnection(); 303 | if (ic != null) { 304 | ic.finishComposingText(); 305 | } 306 | } 307 | } 308 | 309 | /** 310 | * This tells us about completions that the editor has determined based 311 | * on the current text in it. We want to use this in fullscreen mode 312 | * to show the completions ourself, since the editor can not be seen 313 | * in that situation. 314 | */ 315 | @Override public void onDisplayCompletions(CompletionInfo[] completions) { 316 | if (mCompletionOn) { 317 | mCompletions = completions; 318 | if (completions == null) { 319 | setSuggestions(null, false, false); 320 | return; 321 | } 322 | 323 | List stringList = new ArrayList(); 324 | for (int i = 0; i < completions.length; i++) { 325 | CompletionInfo ci = completions[i]; 326 | if (ci != null) stringList.add(ci.getText().toString()); 327 | } 328 | setSuggestions(stringList, true, true); 329 | } 330 | } 331 | 332 | /** 333 | * This translates incoming hard key events in to edit operations on an 334 | * InputConnection. It is only needed when using the 335 | * PROCESS_HARD_KEYS option. 336 | */ 337 | private boolean translateKeyDown(int keyCode, KeyEvent event) { 338 | mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState, 339 | keyCode, event); 340 | int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState)); 341 | mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState); 342 | InputConnection ic = getCurrentInputConnection(); 343 | if (c == 0 || ic == null) { 344 | return false; 345 | } 346 | 347 | boolean dead = false; 348 | 349 | if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) { 350 | dead = true; 351 | c = c & KeyCharacterMap.COMBINING_ACCENT_MASK; 352 | } 353 | 354 | if (mComposing.length() > 0) { 355 | char accent = mComposing.charAt(mComposing.length() -1 ); 356 | int composed = KeyEvent.getDeadChar(accent, c); 357 | 358 | if (composed != 0) { 359 | c = composed; 360 | mComposing.setLength(mComposing.length()-1); 361 | } 362 | } 363 | 364 | onKey(c, null); 365 | 366 | return true; 367 | } 368 | 369 | /** 370 | * Use this to monitor key events being delivered to the application. 371 | * We get first crack at them, and can either resume them or let them 372 | * continue to the app. 373 | */ 374 | @Override public boolean onKeyDown(int keyCode, KeyEvent event) { 375 | 376 | switch (keyCode) { 377 | case KeyEvent.KEYCODE_BACK: 378 | // The InputMethodService already takes care of the back 379 | // key for us, to dismiss the input method if it is shown. 380 | // However, our keyboard could be showing a pop-up window 381 | // that back should dismiss, so we first allow it to do that. 382 | if (event.getRepeatCount() == 0 && mInputView != null) { 383 | if (mInputView.handleBack()) { 384 | return true; 385 | } 386 | } 387 | break; 388 | 389 | case KeyEvent.KEYCODE_DEL: 390 | 391 | // Special handling of the delete key: if we currently are 392 | // composing text for the user, we want to modify that instead 393 | // of let the application to the delete itself. 394 | if (mComposing.length() > 0) { 395 | onKey(Keyboard.KEYCODE_DELETE, null); 396 | return true; 397 | } 398 | break; 399 | 400 | case KeyEvent.KEYCODE_ENTER: 401 | // Let the underlying text editor always handle these. 402 | return false; 403 | default: 404 | // For all other keys, if we want to do transformations on 405 | // text being entered with a hard keyboard, we need to process 406 | // it and do the appropriate action. 407 | /* 408 | if (PROCESS_HARD_KEYS) { 409 | if (keyCode == KeyEvent.KEYCODE_SPACE 410 | && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) { 411 | // A silly example: in our input method, Alt+Space 412 | // is a shortcut for 'android' in lower case. 413 | InputConnection ic = getCurrentInputConnection(); 414 | if (ic != null) { 415 | // First, tell the editor that it is no longer in the 416 | // shift state, since we are consuming this. 417 | ic.clearMetaKeyStates(KeyEvent.META_ALT_ON); 418 | keyDownUp(KeyEvent.KEYCODE_A); 419 | keyDownUp(KeyEvent.KEYCODE_N); 420 | keyDownUp(KeyEvent.KEYCODE_D); 421 | keyDownUp(KeyEvent.KEYCODE_R); 422 | keyDownUp(KeyEvent.KEYCODE_O); 423 | keyDownUp(KeyEvent.KEYCODE_I); 424 | keyDownUp(KeyEvent.KEYCODE_D); 425 | // And we consume this event. 426 | return true; 427 | } 428 | } 429 | if (mPredictionOn && translateKeyDown(keyCode, event)) { 430 | return true; 431 | } 432 | }*/ 433 | } 434 | 435 | return super.onKeyDown(keyCode, event); 436 | } 437 | 438 | /** 439 | * Use this to monitor key events being delivered to the application. 440 | * We get first crack at them, and can either resume them or let them 441 | * continue to the app. 442 | */ 443 | @Override public boolean onKeyUp(int keyCode, KeyEvent event) { 444 | // If we want to do transformations on text being entered with a hard 445 | // keyboard, we need to process the up events to update the meta key 446 | // state we are tracking. 447 | if (PROCESS_HARD_KEYS) { 448 | if (mPredictionOn) { 449 | mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, 450 | keyCode, event); 451 | } 452 | } 453 | 454 | 455 | return super.onKeyUp(keyCode, event); 456 | } 457 | 458 | /** 459 | * Helper function to commit any text being composed in to the editor. 460 | */ 461 | private void commitTyped(InputConnection inputConnection) { 462 | if (mComposing.length() > 0) { 463 | inputConnection.commitText(mComposing, mComposing.length()); 464 | mComposing.setLength(0); 465 | updateCandidates(); 466 | } 467 | } 468 | 469 | /** 470 | * Helper to update the shift state of our keyboard based on the initial 471 | * editor state. 472 | */ 473 | private void updateShiftKeyState(EditorInfo attr) { 474 | if (attr != null 475 | && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) { 476 | int caps = 0; 477 | EditorInfo ei = getCurrentInputEditorInfo(); 478 | if (ei != null && ei.inputType != InputType.TYPE_NULL) { 479 | caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); 480 | } 481 | mInputView.setShifted(mCapsLock || caps != 0); 482 | } 483 | } 484 | 485 | /** 486 | * Helper to determine if a given character code is alphabetic. 487 | */ 488 | private boolean isAlphabet(int code) { 489 | if (Character.isLetter(code)) { 490 | return true; 491 | } else { 492 | return false; 493 | } 494 | } 495 | 496 | /** 497 | * Helper to send a key down / key up pair to the current editor. 498 | */ 499 | private void keyDownUp(int keyEventCode) { 500 | getCurrentInputConnection().sendKeyEvent( 501 | new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); 502 | getCurrentInputConnection().sendKeyEvent( 503 | new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); 504 | } 505 | 506 | /** 507 | * Helper to send a character to the editor as raw key events. 508 | */ 509 | private void sendKey(int keyCode) { 510 | switch (keyCode) { 511 | case '\n': 512 | keyDownUp(KeyEvent.KEYCODE_ENTER); 513 | break; 514 | default: 515 | if (keyCode >= '0' && keyCode <= '9') { 516 | keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0); 517 | } else { 518 | getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1); 519 | } 520 | break; 521 | } 522 | } 523 | 524 | // Implementation of KeyboardViewListener 525 | 526 | public void onKey(int primaryCode, int[] keyCodes) { 527 | Log.d("Test","KEYCODE: " + primaryCode); 528 | if (isWordSeparator(primaryCode)) { 529 | // Handle separator 530 | if (mComposing.length() > 0) { 531 | commitTyped(getCurrentInputConnection()); 532 | } 533 | sendKey(primaryCode); 534 | updateShiftKeyState(getCurrentInputEditorInfo()); 535 | } else if (primaryCode == Keyboard.KEYCODE_DELETE) { 536 | handleBackspace(); 537 | } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { 538 | handleShift(); 539 | } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { 540 | handleClose(); 541 | return; 542 | } else if (primaryCode == LatinKeyboardView.KEYCODE_LANGUAGE_SWITCH) { 543 | handleLanguageSwitch(); 544 | return; 545 | } else if (primaryCode == LatinKeyboardView.KEYCODE_OPTIONS) { 546 | // Show a menu or somethin' 547 | } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE 548 | && mInputView != null) { 549 | Keyboard current = mInputView.getKeyboard(); 550 | if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) { 551 | setLatinKeyboard(mQwertyKeyboard); 552 | } else { 553 | setLatinKeyboard(mSymbolsKeyboard); 554 | mSymbolsKeyboard.setShifted(false); 555 | } 556 | } else { 557 | handleCharacter(primaryCode, keyCodes); 558 | } 559 | } 560 | 561 | public void onText(CharSequence text) { 562 | InputConnection ic = getCurrentInputConnection(); 563 | if (ic == null) return; 564 | ic.beginBatchEdit(); 565 | if (mComposing.length() > 0) { 566 | commitTyped(ic); 567 | } 568 | ic.commitText(text, 0); 569 | ic.endBatchEdit(); 570 | updateShiftKeyState(getCurrentInputEditorInfo()); 571 | } 572 | 573 | /** 574 | * Update the list of available candidates from the current composing 575 | * text. This will need to be filled in by however you are determining 576 | * candidates. 577 | */ 578 | private void updateCandidates() { 579 | if (!mCompletionOn) { 580 | if (mComposing.length() > 0) { 581 | ArrayList list = new ArrayList(); 582 | //list.add(mComposing.toString()); 583 | Log.d("SoftKeyboard", "REQUESTING: " + mComposing.toString()); 584 | mScs.getSentenceSuggestions(new TextInfo[] {new TextInfo(mComposing.toString())}, 5); 585 | setSuggestions(list, true, true); 586 | } else { 587 | setSuggestions(null, false, false); 588 | } 589 | } 590 | } 591 | 592 | public void setSuggestions(List suggestions, boolean completions, 593 | boolean typedWordValid) { 594 | if (suggestions != null && suggestions.size() > 0) { 595 | setCandidatesViewShown(true); 596 | } else if (isExtractViewShown()) { 597 | setCandidatesViewShown(true); 598 | } 599 | mSuggestions = suggestions; 600 | if (mCandidateView != null) { 601 | mCandidateView.setSuggestions(suggestions, completions, typedWordValid); 602 | } 603 | } 604 | 605 | private void handleBackspace() { 606 | final int length = mComposing.length(); 607 | if (length > 1) { 608 | mComposing.delete(length - 1, length); 609 | getCurrentInputConnection().setComposingText(mComposing, 1); 610 | updateCandidates(); 611 | } else if (length > 0) { 612 | mComposing.setLength(0); 613 | getCurrentInputConnection().commitText("", 0); 614 | updateCandidates(); 615 | } else { 616 | keyDownUp(KeyEvent.KEYCODE_DEL); 617 | } 618 | updateShiftKeyState(getCurrentInputEditorInfo()); 619 | } 620 | 621 | private void handleShift() { 622 | if (mInputView == null) { 623 | return; 624 | } 625 | 626 | Keyboard currentKeyboard = mInputView.getKeyboard(); 627 | if (mQwertyKeyboard == currentKeyboard) { 628 | // Alphabet keyboard 629 | checkToggleCapsLock(); 630 | mInputView.setShifted(mCapsLock || !mInputView.isShifted()); 631 | } else if (currentKeyboard == mSymbolsKeyboard) { 632 | mSymbolsKeyboard.setShifted(true); 633 | setLatinKeyboard(mSymbolsShiftedKeyboard); 634 | mSymbolsShiftedKeyboard.setShifted(true); 635 | } else if (currentKeyboard == mSymbolsShiftedKeyboard) { 636 | mSymbolsShiftedKeyboard.setShifted(false); 637 | setLatinKeyboard(mSymbolsKeyboard); 638 | mSymbolsKeyboard.setShifted(false); 639 | } 640 | } 641 | 642 | private void handleCharacter(int primaryCode, int[] keyCodes) { 643 | if (isInputViewShown()) { 644 | if (mInputView.isShifted()) { 645 | primaryCode = Character.toUpperCase(primaryCode); 646 | } 647 | } 648 | if (mPredictionOn) { 649 | mComposing.append((char) primaryCode); 650 | getCurrentInputConnection().setComposingText(mComposing, 1); 651 | updateShiftKeyState(getCurrentInputEditorInfo()); 652 | updateCandidates(); 653 | } else { 654 | getCurrentInputConnection().commitText( 655 | String.valueOf((char) primaryCode), 1); 656 | } 657 | } 658 | 659 | private void handleClose() { 660 | commitTyped(getCurrentInputConnection()); 661 | requestHideSelf(0); 662 | mInputView.closing(); 663 | } 664 | 665 | private IBinder getToken() { 666 | final Dialog dialog = getWindow(); 667 | if (dialog == null) { 668 | return null; 669 | } 670 | final Window window = dialog.getWindow(); 671 | if (window == null) { 672 | return null; 673 | } 674 | return window.getAttributes().token; 675 | } 676 | 677 | private void handleLanguageSwitch() { 678 | mInputMethodManager.switchToNextInputMethod(getToken(), false /* onlyCurrentIme */); 679 | } 680 | 681 | private void checkToggleCapsLock() { 682 | long now = System.currentTimeMillis(); 683 | if (mLastShiftTime + 800 > now) { 684 | mCapsLock = !mCapsLock; 685 | mLastShiftTime = 0; 686 | } else { 687 | mLastShiftTime = now; 688 | } 689 | } 690 | 691 | private String getWordSeparators() { 692 | return mWordSeparators; 693 | } 694 | 695 | public boolean isWordSeparator(int code) { 696 | String separators = getWordSeparators(); 697 | return separators.contains(String.valueOf((char)code)); 698 | } 699 | 700 | public void pickDefaultCandidate() { 701 | pickSuggestionManually(0); 702 | } 703 | 704 | public void pickSuggestionManually(int index) { 705 | if (mCompletionOn && mCompletions != null && index >= 0 706 | && index < mCompletions.length) { 707 | CompletionInfo ci = mCompletions[index]; 708 | getCurrentInputConnection().commitCompletion(ci); 709 | if (mCandidateView != null) { 710 | mCandidateView.clear(); 711 | } 712 | updateShiftKeyState(getCurrentInputEditorInfo()); 713 | } else if (mComposing.length() > 0) { 714 | 715 | if (mPredictionOn && mSuggestions != null && index >= 0) { 716 | mComposing.replace(0, mComposing.length(), mSuggestions.get(index)); 717 | } 718 | commitTyped(getCurrentInputConnection()); 719 | 720 | } 721 | } 722 | 723 | public void swipeRight() { 724 | Log.d("SoftKeyboard", "Swipe right"); 725 | if (mCompletionOn || mPredictionOn) { 726 | pickDefaultCandidate(); 727 | } 728 | } 729 | 730 | public void swipeLeft() { 731 | Log.d("SoftKeyboard", "Swipe left"); 732 | handleBackspace(); 733 | } 734 | 735 | public void swipeDown() { 736 | handleClose(); 737 | } 738 | 739 | public void swipeUp() { 740 | } 741 | 742 | public void onPress(int primaryCode) { 743 | 744 | } 745 | 746 | public void onRelease(int primaryCode) { 747 | 748 | } 749 | /** 750 | * http://www.tutorialspoint.com/android/android_spelling_checker.htm 751 | * @param results results 752 | */ 753 | @Override 754 | public void onGetSuggestions(SuggestionsInfo[] results) { 755 | final StringBuilder sb = new StringBuilder(); 756 | 757 | for (int i = 0; i < results.length; ++i) { 758 | // Returned suggestions are contained in SuggestionsInfo 759 | final int len = results[i].getSuggestionsCount(); 760 | sb.append('\n'); 761 | 762 | for (int j = 0; j < len; ++j) { 763 | sb.append("," + results[i].getSuggestionAt(j)); 764 | } 765 | 766 | sb.append(" (" + len + ")"); 767 | } 768 | Log.d("SoftKeyboard", "SUGGESTIONS: " + sb.toString()); 769 | } 770 | private static final int NOT_A_LENGTH = -1; 771 | 772 | private void dumpSuggestionsInfoInternal( 773 | final List sb, final SuggestionsInfo si, final int length, final int offset) { 774 | // Returned suggestions are contained in SuggestionsInfo 775 | final int len = si.getSuggestionsCount(); 776 | for (int j = 0; j < len; ++j) { 777 | sb.add(si.getSuggestionAt(j)); 778 | } 779 | } 780 | 781 | @Override 782 | public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { 783 | Log.d("SoftKeyboard", "onGetSentenceSuggestions"); 784 | final List sb = new ArrayList<>(); 785 | for (int i = 0; i < results.length; ++i) { 786 | final SentenceSuggestionsInfo ssi = results[i]; 787 | for (int j = 0; j < ssi.getSuggestionsCount(); ++j) { 788 | dumpSuggestionsInfoInternal( 789 | sb, ssi.getSuggestionsInfoAt(j), ssi.getOffsetAt(j), ssi.getLengthAt(j)); 790 | } 791 | } 792 | Log.d("SoftKeyboard", "SUGGESTIONS: " + sb.toString()); 793 | setSuggestions(sb, true, true); 794 | } 795 | } 796 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_en_gb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/icon_en_gb.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon_en_us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/icon_en_us.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_language_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_language_switch.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_return.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_return.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_shift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_shift.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/sym_keyboard_space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/drawable-hdpi/sym_keyboard_space.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/key_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/layout/input.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/keyboard_popup_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 19 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 46dip 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | #3F51B5 22 | #303F9F 23 | #FF4081 24 | #FF000000 25 | #000000 26 | #ff808080 27 | #bbffffff 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 52dip 23 | 20sp 24 | 16sp 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | Sample Soft Keyboard 23 | 24 | 25 | \u0020.,;:!?\n()[]*&@{}/<>_+=|" 26 | 27 | 28 | Go 29 | Next 30 | Send 31 | 32 | 33 | %s 34 | English (GB) 35 | 36 | 37 | Sample Soft Keyboard Settings 38 | Input languages 39 | Select input languages 40 | General 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/ime_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/xml/keyboard_popup_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/method.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 27 | 32 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/xml/qwerty.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 73 | 74 | 78 | 80 | 82 | 84 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/main/res/xml/symbols.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 73 | 74 | 78 | 80 | 82 | 83 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/xml/symbols_shift.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 73 | 74 | 78 | 80 | 82 | 83 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/test/java/com/blackcj/customkeyboard/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.blackcj.customkeyboard; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.5.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /keyboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackcj/AndroidCustomKeyboard/72e5885e91a9fd41c86e018de49be719a1f47370/keyboard.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------