Skip to content

Commit 8ca647f

Browse files
authored
Merge pull request #9 from keymapperorg/fix/1235-android-14-key-event-delay
#1235 android 14 key event delay
2 parents 72b9192 + 781f0db commit 8ca647f

6 files changed

Lines changed: 188 additions & 58 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
1818
coreApp="true"
1919
package="io.github.sds100.keymapper.inputmethod.latin">
20-
2120
<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
2221
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
2322
<uses-permission android:name="android.permission.VIBRATE" />
2423
<uses-permission android:name="android.permission.WRITE_USER_DICTIONARY" />
25-
<uses-permission android:name="io.github.sds100.keymapper.KEY_EVENT_RECEIVER" />
24+
<uses-permission android:name="io.github.sds100.keymapper.KEY_EVENT_RELAY_SERVICE" />
25+
<uses-permission android:name="io.github.sds100.keymapper.debug.KEY_EVENT_RELAY_SERVICE" />
26+
<uses-permission android:name="io.github.sds100.keymapper.ci.KEY_EVENT_RELAY_SERVICE" />
2627

2728
<!-- A signature-protected permission to ask AOSP Keyboard to close the software keyboard.
2829
To use this, add the following line into calling application's AndroidManifest.xml

app/src/main/aidl/io/github/sds100/keymapper/api/IKeyEventReceiver.aidl

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.github.sds100.keymapper.api;
2+
3+
import android.view.KeyEvent;
4+
import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback;
5+
6+
interface IKeyEventRelayService {
7+
/**
8+
* Send a key event to the target package that is registered with
9+
* a callback.
10+
*/
11+
boolean sendKeyEvent(in KeyEvent event, in String targetPackageName);
12+
13+
/**
14+
* Register a callback to receive key events from this relay service. The service
15+
* checks the process uid of the caller to this method and only permits certain applications
16+
* from connecting.
17+
*/
18+
void registerCallback(IKeyEventRelayServiceCallback client);
19+
20+
/**
21+
* Unregister a callback to receive key events from this relay service. The service
22+
* checks the process uid of the caller to this method and only permits certain applications
23+
* from connecting.
24+
*/
25+
void unregisterCallback();
26+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.github.sds100.keymapper.api;
2+
3+
import android.view.KeyEvent;
4+
5+
interface IKeyEventRelayServiceCallback {
6+
boolean onKeyEvent(in KeyEvent event, in String sourcePackageName);
7+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package io.github.sds100.keymapper.inputmethod.latin
2+
3+
import android.content.ComponentName
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.ServiceConnection
7+
import android.os.DeadObjectException
8+
import android.os.IBinder
9+
import android.util.Log
10+
import android.view.KeyEvent
11+
import io.github.sds100.keymapper.api.IKeyEventRelayService
12+
import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback
13+
14+
/**
15+
* This handles connecting to the relay service and exposes an interface
16+
* so other parts of the app can get a reference to the service even when it isn't
17+
* bound yet.
18+
*
19+
* @param servicePackageName This is the package name of the key mapper app to connect to to listen
20+
* to key events. This is needed because the .debug and .ci key mapper builds are commonly
21+
* used.
22+
*/
23+
class KeyEventRelayServiceWrapperImpl(
24+
context: Context,
25+
private val servicePackageName: String,
26+
private val callback: IKeyEventRelayServiceCallback,
27+
) : KeyEventRelayServiceWrapper {
28+
private val ctx: Context = context.applicationContext
29+
30+
private val keyEventRelayServiceLock: Any = Any()
31+
private var keyEventRelayService: IKeyEventRelayService? = null
32+
33+
private val serviceConnection: ServiceConnection =
34+
object : ServiceConnection {
35+
override fun onServiceConnected(
36+
name: ComponentName?,
37+
service: IBinder?,
38+
) {
39+
synchronized(keyEventRelayServiceLock) {
40+
keyEventRelayService = IKeyEventRelayService.Stub.asInterface(service)
41+
Log.e(LatinIME.TAG, "Register callback")
42+
keyEventRelayService?.registerCallback(callback)
43+
}
44+
}
45+
46+
override fun onServiceDisconnected(name: ComponentName?) {
47+
synchronized(keyEventRelayServiceLock) {
48+
keyEventRelayService?.unregisterCallback()
49+
keyEventRelayService = null
50+
}
51+
}
52+
}
53+
54+
override fun sendKeyEvent(
55+
event: KeyEvent?,
56+
targetPackageName: String?,
57+
): Boolean {
58+
synchronized(keyEventRelayServiceLock) {
59+
if (keyEventRelayService == null) {
60+
return false
61+
}
62+
63+
try {
64+
return keyEventRelayService!!.sendKeyEvent(event, targetPackageName)
65+
} catch (e: DeadObjectException) {
66+
keyEventRelayService = null
67+
return false
68+
}
69+
}
70+
}
71+
72+
fun bind() {
73+
try {
74+
val relayServiceIntent = Intent()
75+
val component =
76+
ComponentName(servicePackageName, "io.github.sds100.keymapper.api.KeyEventRelayService")
77+
relayServiceIntent.setComponent(component)
78+
ctx.bindService(relayServiceIntent, serviceConnection, 0)
79+
} catch (e: SecurityException) {
80+
Log.e(LatinIME.TAG, e.toString())
81+
}
82+
}
83+
84+
fun unbind() {
85+
try {
86+
ctx.unbindService(serviceConnection)
87+
} catch (e: DeadObjectException) {
88+
// do nothing
89+
}
90+
}
91+
}
92+
93+
interface KeyEventRelayServiceWrapper {
94+
fun sendKeyEvent(
95+
event: KeyEvent?,
96+
targetPackageName: String?,
97+
): Boolean
98+
}

app/src/main/java/io/github/sds100/keymapper/inputmethod/latin/LatinIME.java

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,13 @@
1616

1717
package io.github.sds100.keymapper.inputmethod.latin;
1818

19-
import static io.github.sds100.keymapper.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII;
20-
import static io.github.sds100.keymapper.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
21-
import static io.github.sds100.keymapper.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
22-
2319
import android.app.AlertDialog;
2420
import android.content.BroadcastReceiver;
25-
import android.content.ComponentName;
2621
import android.content.Context;
2722
import android.content.DialogInterface;
2823
import android.content.DialogInterface.OnClickListener;
2924
import android.content.Intent;
3025
import android.content.IntentFilter;
31-
import android.content.ServiceConnection;
3226
import android.content.res.Configuration;
3327
import android.content.res.Resources;
3428
import android.graphics.Color;
@@ -39,7 +33,6 @@
3933
import android.os.IBinder;
4034
import android.os.Message;
4135
import android.os.Process;
42-
import android.os.RemoteException;
4336
import android.text.InputType;
4437
import android.util.Log;
4538
import android.util.PrintWriterPrinter;
@@ -65,7 +58,7 @@
6558

6659
import javax.annotation.Nonnull;
6760

68-
import io.github.sds100.keymapper.api.IKeyEventReceiver;
61+
import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback;
6962
import io.github.sds100.keymapper.inputmethod.accessibility.AccessibilityUtils;
7063
import io.github.sds100.keymapper.inputmethod.annotations.UsedForTesting;
7164
import io.github.sds100.keymapper.inputmethod.compat.EditorInfoCompatUtils;
@@ -108,6 +101,10 @@
108101
import io.github.sds100.keymapper.inputmethod.latin.utils.SubtypeLocaleUtils;
109102
import io.github.sds100.keymapper.inputmethod.latin.utils.ViewLayoutUtils;
110103

104+
import static io.github.sds100.keymapper.inputmethod.latin.common.Constants.ImeOption.FORCE_ASCII;
105+
import static io.github.sds100.keymapper.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
106+
import static io.github.sds100.keymapper.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
107+
111108
/**
112109
* Input method implementation for Qwerty'ish keyboard.
113110
*/
@@ -167,7 +164,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
167164
private SuggestionStripView mSuggestionStripView;
168165

169166
private RichInputMethodManager mRichImm;
170-
@UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
167+
@UsedForTesting
168+
final KeyboardSwitcher mKeyboardSwitcher;
171169
private final SubtypeState mSubtypeState = new SubtypeState();
172170
private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector;
173171
private StatsUtilsManager mStatsUtilsManager;
@@ -199,6 +197,7 @@ public void onReceive(Context context, Intent intent) {
199197
}
200198
}
201199
}
200+
202201
final HideSoftInputReceiver mHideSoftInputReceiver = new HideSoftInputReceiver(this);
203202

204203
final static class RestartAfterDeviceUnlockReceiver extends BroadcastReceiver {
@@ -220,22 +219,19 @@ public void onReceive(Context context, Intent intent) {
220219

221220
final RestartAfterDeviceUnlockReceiver mRestartAfterDeviceUnlockReceiver = new RestartAfterDeviceUnlockReceiver();
222221

223-
private final Object mKeyEventReceiverLock = new Object();
224-
private IKeyEventReceiver mKeyEventReceiverBinder = null;
225-
226-
private final ServiceConnection mKeyEventReceiverConnection = new ServiceConnection() {
222+
private KeyEventRelayServiceWrapperImpl mKeyEventRelayServiceWrapperRelease;
223+
private KeyEventRelayServiceWrapperImpl mKeyEventRelayServiceWrapperDebug;
224+
private KeyEventRelayServiceWrapperImpl mKeyEventRelayServiceWrapperCi;
225+
private IKeyEventRelayServiceCallback mKeyEventRelayServiceCallback = new IKeyEventRelayServiceCallback.Stub() {
227226
@Override
228-
public void onServiceConnected(ComponentName name, IBinder service) {
229-
synchronized (mKeyEventReceiverLock) {
230-
mKeyEventReceiverBinder = IKeyEventReceiver.Stub.asInterface(service);
231-
}
232-
}
227+
public boolean onKeyEvent(KeyEvent event, String sourcePackageName) {
228+
InputConnection ic = getCurrentInputConnection();
233229

234-
@Override
235-
public void onServiceDisconnected(ComponentName name) {
236-
synchronized (mKeyEventReceiverLock) {
237-
mKeyEventReceiverBinder = null;
230+
if (ic == null) {
231+
return false;
238232
}
233+
234+
return ic.sendKeyEvent(event);
239235
}
240236
};
241237

@@ -438,7 +434,7 @@ public void handleMessage(final Message msg) {
438434
latinIme.deallocateMemory();
439435
break;
440436
case MSG_SWITCH_LANGUAGE_AUTOMATICALLY:
441-
latinIme.switchLanguage((InputMethodSubtype)msg.obj);
437+
latinIme.switchLanguage((InputMethodSubtype) msg.obj);
442438
break;
443439
case MSG_UPDATE_CLIPBOARD_PINNED_CLIPS:
444440
@SuppressWarnings("unchecked")
@@ -782,14 +778,27 @@ public void onCreate() {
782778

783779
registerReceiver(mKeyMapperBroadcastReceiver, keyMapperIntentFilter);
784780

785-
try {
786-
ComponentName keyEventReceiverComponent = new ComponentName("io.github.sds100.keymapper", "io.github.sds100.keymapper.api.KeyEventReceiver");
787-
Intent keyEventReceiverServiceIntent = new Intent();
788-
keyEventReceiverServiceIntent.setComponent(keyEventReceiverComponent);
789-
bindService(keyEventReceiverServiceIntent, mKeyEventReceiverConnection, 0);
790-
} catch (SecurityException e) {
791-
Log.e(TAG, e.toString());
792-
}
781+
// Connect to the different key mapper build types.
782+
mKeyEventRelayServiceWrapperRelease =
783+
new KeyEventRelayServiceWrapperImpl(
784+
getApplicationContext(),
785+
"io.github.sds100.keymapper",
786+
mKeyEventRelayServiceCallback);
787+
mKeyEventRelayServiceWrapperRelease.bind();
788+
789+
mKeyEventRelayServiceWrapperDebug =
790+
new KeyEventRelayServiceWrapperImpl(
791+
getApplicationContext(),
792+
"io.github.sds100.keymapper.debug",
793+
mKeyEventRelayServiceCallback);
794+
mKeyEventRelayServiceWrapperDebug.bind();
795+
796+
mKeyEventRelayServiceWrapperCi =
797+
new KeyEventRelayServiceWrapperImpl(
798+
getApplicationContext(),
799+
"io.github.sds100.keymapper.ci",
800+
mKeyEventRelayServiceCallback);
801+
mKeyEventRelayServiceWrapperCi.bind();
793802

794803
StatsUtils.onCreate(mSettings.getCurrent(), mRichImm);
795804
}
@@ -903,7 +912,9 @@ public void onDestroy() {
903912
unregisterReceiver(mRestartAfterDeviceUnlockReceiver);
904913
unregisterReceiver(mKeyMapperBroadcastReceiver);
905914
mStatsUtilsManager.onDestroy(this /* context */);
906-
unbindService(mKeyEventReceiverConnection);
915+
mKeyEventRelayServiceWrapperRelease.unbind();
916+
mKeyEventRelayServiceWrapperDebug.unbind();
917+
mKeyEventRelayServiceWrapperCi.unbind();
907918
super.onDestroy();
908919
}
909920

@@ -1483,7 +1494,7 @@ public int getCurrentRecapitalizeState() {
14831494

14841495
/**
14851496
* @param codePoints code points to get coordinates for.
1486-
* @return x,y coordinates for this keyboard, as a flattened array.
1497+
* @return x, y coordinates for this keyboard, as a flattened array.
14871498
*/
14881499
public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) {
14891500
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
@@ -1528,7 +1539,7 @@ public void onMovePointer(int steps) {
15281539
// for RTL languages we want to invert pointer movement
15291540
if (mRichImm.getCurrentSubtype().isRtlSubtype())
15301541
steps = -steps;
1531-
1542+
15321543
mInputLogic.finishInput();
15331544
if (steps < 0) {
15341545
int availableCharacters = mInputLogic.mConnection.getTextBeforeCursor(64, 0).length();
@@ -1687,6 +1698,7 @@ public void onCancelBatchInput() {
16871698
* IME for the full gesture, possibly updating the TextView to reflect the first suggestion.
16881699
* <p>
16891700
* This method must be run on the UI Thread.
1701+
*
16901702
* @param suggestedWords suggested words by the IME for the full gesture.
16911703
*/
16921704
public void onTailBatchInputResultShown(final SuggestedWords suggestedWords) {
@@ -1836,6 +1848,7 @@ void loadKeyboard() {
18361848
* After an input transaction has been executed, some state must be updated. This includes
18371849
* the shift state of the keyboard and suggestions. This method looks at the finished
18381850
* inputTransaction to find out what is necessary and updates the state accordingly.
1851+
*
18391852
* @param inputTransaction The transaction that has been executed.
18401853
*/
18411854
private void updateStateAfterInputTransaction(final InputTransaction inputTransaction) {
@@ -1923,13 +1936,9 @@ private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
19231936
@Override
19241937
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
19251938

1926-
if (mKeyEventReceiverBinder != null) {
1927-
try {
1928-
if (mKeyEventReceiverBinder.onKeyEvent(keyEvent)) {
1929-
return true;
1930-
}
1931-
} catch (RemoteException e) {
1932-
1939+
if (mKeyEventRelayServiceWrapperDebug != null) {
1940+
if (mKeyEventRelayServiceWrapperDebug.sendKeyEvent(keyEvent, "io.github.sds100.keymapper")) {
1941+
return true;
19331942
}
19341943
}
19351944

@@ -1959,13 +1968,9 @@ public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
19591968

19601969
@Override
19611970
public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
1962-
if (mKeyEventReceiverBinder != null) {
1963-
try {
1964-
if (mKeyEventReceiverBinder.onKeyEvent(keyEvent)) {
1965-
return true;
1966-
}
1967-
} catch (RemoteException e) {
1968-
1971+
if (mKeyEventRelayServiceWrapperDebug != null) {
1972+
if (mKeyEventRelayServiceWrapperDebug.sendKeyEvent(keyEvent, "io.github.sds100.keymapper")) {
1973+
return true;
19691974
}
19701975
}
19711976

@@ -2024,7 +2029,7 @@ private void showSubtypeSelectorAndSettings() {
20242029
final CharSequence title = getString(R.string.english_ime_input_options);
20252030
// TODO: Should use new string "Select active input modes".
20262031
final CharSequence languageSelectionTitle = getString(R.string.language_selection_title);
2027-
final CharSequence[] items = new CharSequence[] {
2032+
final CharSequence[] items = new CharSequence[]{
20282033
languageSelectionTitle,
20292034
getString(ApplicationUtils.getActivityTitleResId(this, SettingsActivity.class))
20302035
};

0 commit comments

Comments
 (0)