├── .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 |
7 |
8 |
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 | 
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
--------------------------------------------------------------------------------