Skip to content

Commit 517326e

Browse files
committed
Merge branch 'sweenwolf-master'
2 parents 4a30d3a + 68b2aa4 commit 517326e

17 files changed

+352
-355
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ If you have gotten yourself in this situation, see FAQ.
7777
- Disable Boss Key (If you have a full size remote, you don't need to keep a boss key. Info key and color buttons are sufficient, read how to use section for more info)
7878
- Will Boss Key Toggle (When remotes don't allow long pressing, this will allow people to cycle through various modes on key press in the order: Dpad -> Mouse -> Scroll -> Dpad) `*`
7979
- Override Activation Key (Select this to set a custom keyCode for bossKey)
80+
- Scrolls when mouse touches the edges (thanks to [@sweenwolf](https://github.com/sweenwolf))
81+
- Automatically detect boss key code (again thanks to [@sweenwolf](https://github.com/sweenwolf) !)
8082

8183
` *` Not available on FlipPhone version
8284
`**` Not available on FlipPhones with Android 6.
@@ -137,7 +139,7 @@ All kinds of contributions are welcome. Two ways of contributing:
137139

138140
# Credits
139141
Thanks to EVA Facial Mouse for open sourcing their code. I've taken lots of ideas from their codebase. You can check them out at https://github.com/cmauri/eva_facial_mouse
140-
Thanks to @sweenwolf for making this app work on remotes with less buttons, and for the app icons and fully transparent cursor images
142+
Thanks to [@sweenwolf](https://github.com/sweenwolf) for making this app work on remotes with less buttons, and for the app icons, fully transparent cursor images, boss key autodetect and autoscroll on borders!
141143
Thanks to TechDoctorUK for making a demo video
142144
Thanks to @hotcereal_twitter for providing more cursor images. [Link](https://gitter.im/virresh/community?at=6102e7b0d8381a2a839bbcfd).
143145

app/build.gradle

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ android {
1010
applicationId "io.github.virresh.matvt"
1111
minSdkVersion 24
1212
targetSdkVersion 29
13-
versionCode 105
14-
versionName '1.0.5'
13+
versionCode 106
14+
versionName '1.0.6'
1515

1616
}
1717

@@ -29,6 +29,6 @@ android {
2929

3030
dependencies {
3131

32-
implementation 'androidx.appcompat:appcompat:1.2.0'
33-
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
32+
implementation 'androidx.appcompat:appcompat:1.3.1'
33+
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
3434
}

app/src/main/AndroidManifest.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
android:supportsRtl="true"
2020
android:theme="@style/AppTheme">
2121
<activity
22+
android:screenOrientation="sensorLandscape"
2223
android:name=".gui.GuiActivity"
2324
android:launchMode="singleTop">
2425
<intent-filter>
@@ -28,7 +29,10 @@
2829
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
2930
</intent-filter>
3031
</activity>
31-
32+
<activity
33+
android:screenOrientation="sensorLandscape"
34+
android:name=".helper.KeyDetection"
35+
android:launchMode="singleTop"/>
3236
<service
3337
android:name=".services.MouseEventService"
3438
android:label="Mouse Toggle Service"

app/src/main/java/io/github/virresh/matvt/engine/impl/MouseEmulationEngine.java

+78-36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.github.virresh.matvt.engine.impl;
22

3+
import static io.github.virresh.matvt.helper.Helper.helperContext;
4+
35
import android.accessibilityservice.AccessibilityService;
46
import android.accessibilityservice.GestureDescription;
57
import android.content.Context;
@@ -11,8 +13,10 @@
1113
import android.os.Handler;
1214
import android.util.Log;
1315
import android.view.KeyEvent;
16+
import android.view.View;
1417
import android.view.accessibility.AccessibilityNodeInfo;
1518
import android.view.accessibility.AccessibilityWindowInfo;
19+
import android.view.inputmethod.InputMethodManager;
1620
import android.widget.Toast;
1721

1822
import androidx.annotation.NonNull;
@@ -25,11 +29,13 @@
2529
import java.util.Map;
2630
import java.util.Set;
2731

32+
import io.github.virresh.matvt.helper.Helper;
2833
import io.github.virresh.matvt.view.MouseCursorView;
2934
import io.github.virresh.matvt.view.OverlayView;
3035

3136
public class MouseEmulationEngine {
3237

38+
private static boolean DPAD_SELECT_PRESSED = false;
3339
private static String LOG_TAG = "MOUSE_EMULATION";
3440

3541
CountDownTimer waitToChange;
@@ -43,6 +49,8 @@ public class MouseEmulationEngine {
4349

4450
private final PointerControl mPointerControl;
4551

52+
public static int stuckAtSide = 0;
53+
4654
private int momentumStack;
4755

4856
private boolean isEnabled;
@@ -57,6 +65,8 @@ public class MouseEmulationEngine {
5765

5866
private Handler timerHandler;
5967

68+
private Point DPAD_Center_Init_Point = new Point();
69+
6070
private Runnable previousRunnable;
6171

6272
// tells which keycodes correspond to which pointer movement in scroll and movement mode
@@ -165,6 +175,23 @@ public void run() {
165175
timerHandler.postDelayed(previousRunnable, 0);
166176
}
167177

178+
private void createSwipeForSingle (final PointF originPoint, final int direction) {
179+
if (previousRunnable != null) {
180+
detachPreviousTimer();
181+
}
182+
previousRunnable = new Runnable() {
183+
@Override
184+
public void run() {
185+
mPointerControl.reappear();
186+
mService.dispatchGesture(createSwipe(originPoint, direction, 20 + momentumStack), null, null);
187+
momentumStack += 1;
188+
timerHandler.postDelayed(this, 30);
189+
}
190+
};
191+
timerHandler.postDelayed(previousRunnable, 0);
192+
}
193+
194+
168195
/**
169196
* Auto Disappear mouse after some duration and reset momentum
170197
*/
@@ -282,16 +309,18 @@ else if (keyEvent.getKeyCode() == bossKey && isBossKeyDisabled) {
282309
boolean consumed = false;
283310
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN){
284311
if (scrollCodeMap.containsKey(keyEvent.getKeyCode())) {
285-
if (isInScrollMode || colorSet.contains(keyEvent.getKeyCode())) {
312+
if (isInScrollMode || colorSet.contains(keyEvent.getKeyCode()))
286313
attachGesture(mPointerControl.getPointerLocation(), scrollCodeMap.get(keyEvent.getKeyCode()));
287-
}
288-
else if (movementCodeMap.containsKey(keyEvent.getKeyCode())){
314+
else if (!isInScrollMode && stuckAtSide != 0 && keyEvent.getKeyCode() == stuckAtSide)
315+
createSwipeForSingle(mPointerControl.getCenterPointOfView(), scrollCodeMap.get(keyEvent.getKeyCode()));
316+
else if (movementCodeMap.containsKey(keyEvent.getKeyCode()))
289317
attachTimer(movementCodeMap.get(keyEvent.getKeyCode()));
290-
}
291318
consumed = true;
292319
}
293320
else if(keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
294321
// just consume this event to prevent propagation
322+
DPAD_Center_Init_Point = new Point((int) mPointerControl.getPointerLocation().x, (int) mPointerControl.getPointerLocation().y);
323+
DPAD_SELECT_PRESSED = true;
295324
consumed = true;
296325
}
297326
}
@@ -304,50 +333,63 @@ else if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
304333
consumed = true;
305334
}
306335
else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
336+
DPAD_SELECT_PRESSED = false;
307337
detachPreviousTimer();
308338
// if (keyEvent.getEventTime() - keyEvent.getDownTime() > 500) {
309339
// unreliable long click event if button was pressed for more than 500 ms
310340
int action = AccessibilityNodeInfo.ACTION_CLICK;
311341
Point pInt = new Point((int) mPointerControl.getPointerLocation().x, (int) mPointerControl.getPointerLocation().y);
312-
List<AccessibilityWindowInfo> windowList= mService.getWindows();
313-
boolean wasIME = false, focused = false;
314-
for (AccessibilityWindowInfo window : windowList) {
315-
if (consumed || wasIME) {
316-
break;
317-
}
318-
List<AccessibilityNodeInfo> nodeHierarchy = findNode(window.getRoot(), action, pInt);
319-
for (int i=nodeHierarchy.size()-1; i>=0; i--){
320-
if (consumed || focused) {
321-
break;
322-
};
323-
AccessibilityNodeInfo hitNode = nodeHierarchy.get(i);
324-
List<AccessibilityNodeInfo.AccessibilityAction> availableActions = hitNode.getActionList();
325-
if (availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS)){
326-
focused = hitNode.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
327-
}
328-
// if (hitNode.isFocused() && availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SELECT)){
329-
// hitNode.performAction(AccessibilityNodeInfo.ACTION_SELECT);
330-
// }
331-
// if (hitNode.isFocused() && availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)){
332-
// consumed = hitNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
333-
// }
334-
if (window.getType() == AccessibilityWindowInfo.TYPE_INPUT_METHOD) {
335-
wasIME = true;
336-
consumed = hitNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
342+
if (DPAD_Center_Init_Point.equals(pInt)) {
343+
List<AccessibilityWindowInfo> windowList = mService.getWindows();
344+
boolean wasIME = false, focused = false;
345+
for (AccessibilityWindowInfo window : windowList) {
346+
if (consumed || wasIME) {
337347
break;
338348
}
349+
List<AccessibilityNodeInfo> nodeHierarchy = findNode(window.getRoot(), action, pInt);
350+
for (int i = nodeHierarchy.size() - 1; i >= 0; i--) {
351+
if (consumed || focused) {
352+
break;
353+
}
354+
;
355+
AccessibilityNodeInfo hitNode = nodeHierarchy.get(i);
356+
List<AccessibilityNodeInfo.AccessibilityAction> availableActions = hitNode.getActionList();
357+
if (availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS)) {
358+
focused = hitNode.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
359+
}
360+
if (hitNode.isFocused() && availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SELECT)) {
361+
hitNode.performAction(AccessibilityNodeInfo.ACTION_SELECT);
362+
}
363+
if (hitNode.isFocused() && availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
364+
consumed = hitNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
365+
}
366+
if (window.getType() == AccessibilityWindowInfo.TYPE_INPUT_METHOD && !(hitNode.getPackageName()).toString().contains("leankeyboard")) {
367+
if (hitNode.getPackageName().equals("com.amazon.tv.ime") && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK && helperContext != null) {
368+
InputMethodManager imm = (InputMethodManager) helperContext.getSystemService(Context.INPUT_METHOD_SERVICE);
369+
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
370+
consumed = wasIME = true;
371+
} else {
372+
wasIME = true;
373+
consumed = hitNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
374+
}
375+
break;
376+
}
339377

340-
if ((hitNode.getPackageName().equals("com.google.android.tvlauncher")
341-
&& availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK))) {
342-
if (hitNode.isFocusable()) {
343-
focused = hitNode.performAction(AccessibilityNodeInfo.FOCUS_INPUT);
378+
if ((hitNode.getPackageName().equals("com.google.android.tvlauncher")
379+
&& availableActions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK))) {
380+
if (hitNode.isFocusable()) {
381+
focused = hitNode.performAction(AccessibilityNodeInfo.FOCUS_INPUT);
382+
}
383+
consumed = hitNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
344384
}
345-
consumed = hitNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
346385
}
347386
}
387+
if (!consumed && !wasIME) {
388+
mService.dispatchGesture(createClick(mPointerControl.getPointerLocation(), keyEvent.getEventTime() - keyEvent.getDownTime()), null, null);
389+
}
348390
}
349-
if (!consumed && !wasIME) {
350-
mService.dispatchGesture(createClick(mPointerControl.getPointerLocation(), keyEvent.getEventTime() - keyEvent.getDownTime()), null, null);
391+
else{
392+
//Implement Drag Function here
351393
}
352394
}
353395
}

app/src/main/java/io/github/virresh/matvt/engine/impl/PointerControl.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import android.graphics.PointF;
44
import android.util.Log;
5+
import android.view.KeyEvent;
56
import android.view.View;
67

78
import androidx.annotation.NonNull;
89

9-
import io.github.virresh.matvt.helper.Helper;
1010
import io.github.virresh.matvt.view.MouseCursorView;
1111
import io.github.virresh.matvt.view.OverlayView;
1212

@@ -66,6 +66,7 @@ public void reappear () {
6666
public static boolean isBordered = false;
6767

6868
public void move (int direction, int momentum) {
69+
6970
int movementX = (int) (dirX[direction] * ((momentum)));
7071
int movementY = (int) (dirY[direction] * ((momentum)));
7172

@@ -87,21 +88,30 @@ public void move (int direction, int momentum) {
8788
}
8889

8990
else {
91+
MouseEmulationEngine.stuckAtSide = 0;
9092

9193
if (mPointerLocation.x + movementX >= 0 && mPointerLocation.x + movementX <= mPointerLayerView.getWidth())
9294
mPointerLocation.x += movementX;
9395
else {
94-
if (mPointerLocation.x < (mPointerLayerView.getWidth() / 2f))
96+
if (mPointerLocation.x < (mPointerLayerView.getWidth() / 2f)) {
97+
MouseEmulationEngine.stuckAtSide = KeyEvent.KEYCODE_DPAD_LEFT;
9598
mPointerLocation.x = 0;
96-
else mPointerLocation.x = mPointerLayerView.getWidth();
99+
}
100+
else {
101+
MouseEmulationEngine.stuckAtSide = KeyEvent.KEYCODE_DPAD_RIGHT;
102+
mPointerLocation.x = mPointerLayerView.getWidth();
103+
}
97104
}
98-
99105
if (mPointerLocation.y + movementY >= 0 && mPointerLocation.y + movementY <= mPointerLayerView.getHeight())
100106
mPointerLocation.y += movementY;
101107
else {
102108
if (mPointerLocation.y < (mPointerLayerView.getHeight() / 2f)) {
109+
MouseEmulationEngine.stuckAtSide = KeyEvent.KEYCODE_DPAD_UP;
103110
mPointerLocation.y = 0;
104-
} else mPointerLocation.y = mPointerLayerView.getHeight();
111+
} else {
112+
MouseEmulationEngine.stuckAtSide = KeyEvent.KEYCODE_DPAD_DOWN;
113+
mPointerLocation.y = mPointerLayerView.getHeight();
114+
}
105115
}
106116
}
107117

@@ -112,4 +122,8 @@ public void move (int direction, int momentum) {
112122
PointF getPointerLocation() {
113123
return mPointerLocation;
114124
}
125+
126+
PointF getCenterPointOfView() {
127+
return new PointF(mPointerLayerView.getWidth() / 2f,mPointerLayerView.getWidth() / 2f);
128+
}
115129
}

0 commit comments

Comments
 (0)