From 26fa5cba3a7caf943484addbe07b57db96a8609c Mon Sep 17 00:00:00 2001 From: Iordan Iordanov Date: Sun, 31 Mar 2024 17:27:02 -0400 Subject: [PATCH] Implemented functionality to move mouse pointer with D-PAD on Android TV remote. Implemented a hint how to pull up the keyboard on Android TV when clicking with D-PAD center. --- .../com/iiordanov/bVNC/ConnectionBean.java | 8 +- .../iiordanov/bVNC/RemoteCanvasActivity.java | 48 ++++++++- .../bVNC/input/PointerInputHandler.java | 4 +- .../bVNC/input/RemoteCanvasHandler.java | 6 ++ .../bVNC/input/RemoteClientsInputListener.kt | 4 + .../iiordanov/bVNC/input/RemotePointer.java | 4 +- .../bVNC/input/RemoteRdpPointer.java | 4 +- .../bVNC/input/RemoteSpicePointer.java | 5 +- .../bVNC/input/RemoteVncPointer.java | 3 +- .../bVNC/protocol/RemoteConnection.java | 7 +- .../bVNC/protocol/RemoteOpaqueConnection.kt | 3 +- .../bVNC/protocol/RemoteRdpConnection.kt | 2 +- .../bVNC/protocol/RemoteSpiceConnection.kt | 2 +- .../bVNC/protocol/RemoteVncConnection.kt | 2 +- bVNC/src/main/res/layout-large/canvas.xml | 35 ++++--- bVNC/src/main/res/layout/canvas.xml | 31 ++++-- .../opaque/RemoteClientLibConstants.java | 2 + .../undatech/opaque/input/InputConstants.kt | 13 +++ .../undatech/opaque/input/RemotePointer.java | 98 ++++++++++++++++--- 19 files changed, 222 insertions(+), 59 deletions(-) create mode 100644 remoteClientLib/src/main/java/com/undatech/opaque/input/InputConstants.kt diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/ConnectionBean.java b/bVNC/src/main/java/com/iiordanov/bVNC/ConnectionBean.java index 6dee7d7c5..8fd42fedb 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/ConnectionBean.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/ConnectionBean.java @@ -29,6 +29,7 @@ import com.antlersoft.android.dbimpl.NewInstance; import com.iiordanov.bVNC.input.TouchInputHandlerDirectSwipePan; import com.iiordanov.util.NetworkUtils; +import com.undatech.opaque.util.GeneralUtils; import com.undatech.remoteClientUi.R; import net.sqlcipher.database.SQLiteDatabase; @@ -64,13 +65,14 @@ public ConnectionBean get() { public ConnectionBean(Context context) { String inputMode = TouchInputHandlerDirectSwipePan.ID; - Boolean preferSendingUnicode = false; - + boolean preferSendingUnicode = false; + boolean useDpadAsArrows = true; if (context == null) { context = App.getContext(); } if (context != null) { + useDpadAsArrows = !GeneralUtils.isTv(context); inputMode = Utils.querySharedPreferenceString(context, Constants.defaultInputMethodTag, TouchInputHandlerDirectSwipePan.ID); preferSendingUnicode = Utils.querySharedPreferenceBoolean(context, Constants.preferSendingUnicode); @@ -123,7 +125,7 @@ public ConnectionBean(Context context) { setPrefEncoding(RfbProto.EncodingTight); setScaleMode(ScaleType.MATRIX); setInputMode(inputMode); - setUseDpadAsArrows(true); + setUseDpadAsArrows(useDpadAsArrows); setRotateDpad(false); setUsePortrait(false); setUseLocalCursor(Constants.CURSOR_AUTO); diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/RemoteCanvasActivity.java b/bVNC/src/main/java/com/iiordanov/bVNC/RemoteCanvasActivity.java index 976490c69..c2ad36931 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/RemoteCanvasActivity.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/RemoteCanvasActivity.java @@ -23,6 +23,7 @@ // package com.iiordanov.bVNC; +import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Dialog; @@ -156,6 +157,7 @@ public class RemoteCanvasActivity extends AppCompatActivity implements View rootView; ActionBarHider actionBarHider = new ActionBarHider(); ActionBarShower actionBarShower = new ActionBarShower(); + KeyboardIconShower keyboardIconShower = new KeyboardIconShower(); private Vibrator myVibrator; private RemoteCanvas canvas; private RemoteConnection remoteConnection; @@ -164,6 +166,8 @@ public class RemoteCanvasActivity extends AppCompatActivity implements private TouchInputHandler[] inputModeHandlers; private Connection connection; public RemoteClientsInputListener inputListener; + private ImageButton keyboardIconForAndroidTv; + float keyboardIconForAndroidTvX = Float.MAX_VALUE; /** * This runnable enables immersive mode. @@ -223,6 +227,7 @@ public void run() { private void correctAfterRotation() throws Exception { Log.d(TAG, "correctAfterRotation"); canvas.waitUntilInflated(); + // Its quite common to see NullPointerExceptions here when this function is called // at the point of disconnection. Hence, we catch and ignore the error. float oldScale = canvas.canvasZoomer.getZoomFactor(); @@ -282,6 +287,7 @@ public void onCreate(Bundle icicle) { setContentView(R.layout.canvas); canvas = findViewById(R.id.canvas); + keyboardIconForAndroidTv = findViewById(R.id.keyboardIconForAndroidTv); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { canvas.setDefaultFocusHighlightEnabled(false); @@ -512,6 +518,10 @@ void relayoutViews(View rootView) { // Use the visible display frame of the decor view to compute notch dimensions. int rootViewHeight = rootView.getHeight(); + if (keyboardIconForAndroidTvX == Float.MAX_VALUE) { + keyboardIconForAndroidTvX = keyboardIconForAndroidTv.getX(); + } + int layoutKeysBottom = layoutKeys.getBottom(); int toolbarBottom = toolbar.getBottom(); int rootViewBottom = layoutKeys.getRootView().getBottom(); @@ -1526,6 +1536,13 @@ public void showActionBar() { handler.postAtTime(actionBarHider, SystemClock.uptimeMillis() + hideToolbarDelay); } + public void showKeyboardIcon() { + handler.removeCallbacks(keyboardIconShower); + handler.postAtTime(keyboardIconShower, SystemClock.uptimeMillis() + 50); + handler.removeCallbacks(actionBarHider); + handler.postAtTime(actionBarHider, SystemClock.uptimeMillis() + hideToolbarDelay); + } + @Override public void onTextSelected(String selectedString) { android.util.Log.i(TAG, "onTextSelected called with selectedString: " + selectedString); @@ -1604,16 +1621,24 @@ public RemoteConnection getRemoteConnection() { private class ActionBarHider implements Runnable { public void run() { - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - Log.d(TAG, "ActionBarHider: Hiding ActionBar"); - actionBar.hide(); + if (GeneralUtils.isTv(RemoteCanvasActivity.this)) { + keyboardIconForAndroidTv.setVisibility(View.GONE); + } else { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + Log.d(TAG, "ActionBarHider: Hiding ActionBar"); + actionBar.hide(); + } } } } private class ActionBarShower implements Runnable { public void run() { + showActionBar(); + } + + private void showActionBar() { ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { Log.d(TAG, "ActionBarShower: Showing ActionBar"); @@ -1622,5 +1647,20 @@ public void run() { } } + private class KeyboardIconShower implements Runnable { + public void run() { + if (GeneralUtils.isTv(RemoteCanvasActivity.this)) { + animateKeyboardIconForAndroidTv(); + } + } + private void animateKeyboardIconForAndroidTv() { + keyboardIconForAndroidTv.setVisibility(View.VISIBLE); + Log.d(TAG, "ActionBarHider: keyboardIconForAndroidTv X position to: " + keyboardIconForAndroidTvX); + keyboardIconForAndroidTv.setX(keyboardIconForAndroidTvX); + ObjectAnimator animation = ObjectAnimator.ofFloat(keyboardIconForAndroidTv, "translationX", -100f); + animation.setDuration(1000); + animation.start(); + } + } } diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/PointerInputHandler.java b/bVNC/src/main/java/com/iiordanov/bVNC/input/PointerInputHandler.java index ca00e73c0..9c46866db 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/PointerInputHandler.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/PointerInputHandler.java @@ -3,7 +3,5 @@ import android.view.KeyEvent; public interface PointerInputHandler { - boolean onKeyDownEvent(int keyCode, KeyEvent event); - - boolean onKeyUpEvent(int keyCode, KeyEvent event); + boolean onKeyAsPointerEvent(int keyCode, KeyEvent event); } diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteCanvasHandler.java b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteCanvasHandler.java index 09d84e4e8..5b107c232 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteCanvasHandler.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteCanvasHandler.java @@ -652,6 +652,12 @@ public void handleMessage(final Message msg) { this.post(() -> Toast.makeText(context, Utils.getStringResourceByName(context, messageText), Toast.LENGTH_LONG).show()); break; + case RemoteClientLibConstants.SHOW_KEYBOARD: + ((RemoteCanvasActivity) context).showKeyboard(); + break; + case RemoteClientLibConstants.SHOW_KEYBOARD_ICON: + ((RemoteCanvasActivity) context).showKeyboardIcon(); + break; default: android.util.Log.e(TAG, "Not handling unknown messageId: " + msg.what); break; diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteClientsInputListener.kt b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteClientsInputListener.kt index 9fb4931ea..e1199d773 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteClientsInputListener.kt +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteClientsInputListener.kt @@ -30,6 +30,8 @@ import android.view.MotionEvent import android.view.View import com.iiordanov.bVNC.App import com.iiordanov.bVNC.Constants +import com.iiordanov.bVNC.RemoteCanvasActivity +import com.undatech.opaque.input.InputConstants import com.undatech.opaque.util.GeneralUtils import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -38,6 +40,7 @@ import java.util.concurrent.Executors class RemoteClientsInputListener( val activity: Activity, private val keyInputHandler: KeyInputHandler?, + private val pointerInputHandler: PointerInputHandler?, private val touchInputHandler: TouchInputHandler?, val resetOnScreenKeys: (input: Int) -> Int, private val useDpadAsArrows: Boolean, @@ -54,6 +57,7 @@ class RemoteClientsInputListener( keyCode, evt ) else activity.onKeyUp(keyCode, evt) } else if (isTv && keyCode == KeyEvent.KEYCODE_BACK) { + Log.i(tag, "Not capturing back button on Android TV") return false } try { diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemotePointer.java b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemotePointer.java index 2c23b63d2..774fa28ac 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemotePointer.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemotePointer.java @@ -10,8 +10,8 @@ public abstract class RemotePointer extends com.undatech.opaque.input.RemotePointer { public RemotePointer( RfbConnectable rfbConnectable, Context context, InputCarriable remoteInput, - Viewable canvas, Handler handler, boolean debugLogging + Viewable canvas, Handler handler, boolean useDpadAsPointer, boolean debugLogging ) { - super(rfbConnectable, context, remoteInput, canvas, handler, debugLogging); + super(rfbConnectable, context, remoteInput, canvas, handler, useDpadAsPointer, debugLogging); } } diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteRdpPointer.java b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteRdpPointer.java index 06561c764..275bb1a88 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteRdpPointer.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteRdpPointer.java @@ -26,9 +26,9 @@ public class RemoteRdpPointer extends RemotePointer { public RemoteRdpPointer( RfbConnectable rfbConnectable, Context context, InputCarriable remoteInput, - Viewable canvas, Handler handler, boolean debugLogging + Viewable canvas, Handler handler, boolean useDpadAsPointer, boolean debugLogging ) { - super(rfbConnectable, context, remoteInput, canvas, handler, debugLogging); + super(rfbConnectable, context, remoteInput, canvas, handler, useDpadAsPointer, debugLogging); } private void sendButtonDownOrMoveButtonDown(int x, int y, int metaState) { diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteSpicePointer.java b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteSpicePointer.java index 77e1d2144..2b2070192 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteSpicePointer.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteSpicePointer.java @@ -44,9 +44,10 @@ public RemoteSpicePointer( InputCarriable remoteInput, Viewable canvas, Handler handler, + boolean useDpadAsArrows, boolean debugLogging ) { - super(protocomm, context, remoteInput, canvas, handler, debugLogging); + super(protocomm, context, remoteInput, canvas, handler, useDpadAsArrows, debugLogging); } @Override @@ -140,6 +141,8 @@ private void clearPointerMaskEvent(int x, int y, boolean isMoving, int combinedM * @param isMoving */ private void sendPointerEvent(int x, int y, int metaState, boolean isMoving) { + GeneralUtils.debugLog(this.debugLogging, TAG, "sendPointerEvent: x: " + x + + ", y: " + y + ", metaState: " + metaState + ", isMoving: " + isMoving); int combinedMetaState = metaState | remoteInput.getKeyboard().getMetaState(); diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteVncPointer.java b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteVncPointer.java index 35ef091bf..0b999a95c 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteVncPointer.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/input/RemoteVncPointer.java @@ -44,9 +44,10 @@ public RemoteVncPointer( InputCarriable remoteInput, Viewable canvas, Handler handler, + boolean useDpadAsArrows, boolean debugLogging ) { - super(rfb, context, remoteInput, canvas, handler, debugLogging); + super(rfb, context, remoteInput, canvas, handler, useDpadAsArrows, debugLogging); } @Override diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteConnection.java b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteConnection.java index e987a3c6f..b309163cb 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteConnection.java +++ b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteConnection.java @@ -44,6 +44,7 @@ import com.iiordanov.bVNC.SSHConnection; import com.iiordanov.bVNC.Utils; import com.iiordanov.bVNC.input.KeyInputHandler; +import com.iiordanov.bVNC.input.PointerInputHandler; import com.iiordanov.bVNC.input.RemoteKeyboard; import com.undatech.opaque.Connection; import com.undatech.opaque.InputCarriable; @@ -58,7 +59,7 @@ import java.util.Map; import java.util.Timer; -abstract public class RemoteConnection implements KeyInputHandler, InputCarriable { +abstract public class RemoteConnection implements PointerInputHandler, KeyInputHandler, InputCarriable { private final static String TAG = "RemoteConnection"; @@ -381,4 +382,8 @@ public boolean onKeyUpEvent(int keyCode, KeyEvent e) { public boolean canUpdateColorModelConnected() { return false; } + + public boolean onKeyAsPointerEvent(int keyCode, KeyEvent event) { + return pointer.hardwareButtonsAsMouseEvents(keyCode, event, 0); + } } diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteOpaqueConnection.kt b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteOpaqueConnection.kt index d9d74aaf8..9b12c7b4d 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteOpaqueConnection.kt +++ b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteOpaqueConnection.kt @@ -2,6 +2,7 @@ package com.iiordanov.bVNC.protocol import android.content.Context import android.util.Log +import android.view.KeyEvent import com.iiordanov.bVNC.App import com.iiordanov.bVNC.COLORMODEL import com.iiordanov.bVNC.Constants @@ -35,7 +36,7 @@ open class RemoteOpaqueConnection( !Utils.isFree(context) && connection.isUsbEnabled, App.debugLog ) rfbConn = spiceComm - pointer = RemoteSpicePointer(spiceComm, context, this, canvas, handler, App.debugLog) + pointer = RemoteSpicePointer(spiceComm, context, this, canvas, handler, !connection.useDpadAsArrows, App.debugLog) try { keyboard = RemoteSpiceKeyboard( context.resources, spiceComm, canvas, this, diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteRdpConnection.kt b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteRdpConnection.kt index f9d3f5a16..4edf7762d 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteRdpConnection.kt +++ b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteRdpConnection.kt @@ -32,7 +32,7 @@ class RemoteRdpConnection( App.debugLog ) rfbConn = rdpComm - pointer = RemoteRdpPointer(rfbConn, context, this, canvas, handler, App.debugLog) + pointer = RemoteRdpPointer(rfbConn, context, this, canvas, handler, !connection.useDpadAsArrows, App.debugLog) keyboard = RemoteRdpKeyboard( rdpComm, canvas, this, handler, App.debugLog, connection.preferSendingUnicode diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteSpiceConnection.kt b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteSpiceConnection.kt index 77cd2596c..683921451 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteSpiceConnection.kt +++ b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteSpiceConnection.kt @@ -36,7 +36,7 @@ class RemoteSpiceConnection( App.debugLog ) rfbConn = spiceComm - pointer = RemoteSpicePointer(spiceComm, context, this, canvas, handler, App.debugLog) + pointer = RemoteSpicePointer(spiceComm, context, this, canvas, handler, !connection.useDpadAsArrows, App.debugLog) keyboard = RemoteSpiceKeyboard( context.resources, spiceComm, canvas, this, handler, connection.layoutMap, App.debugLog diff --git a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteVncConnection.kt b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteVncConnection.kt index d71d34d34..4f005c0e7 100644 --- a/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteVncConnection.kt +++ b/bVNC/src/main/java/com/iiordanov/bVNC/protocol/RemoteVncConnection.kt @@ -89,7 +89,7 @@ class RemoteVncConnection( App.debugLog ) rfbConn = rfb - pointer = RemoteVncPointer(rfbConn, context, this, canvas, handler, App.debugLog) + pointer = RemoteVncPointer(rfbConn, context, this, canvas, handler, !connection.useDpadAsArrows, App.debugLog) val rAltAsIsoL3Shift = Utils.querySharedPreferenceBoolean( this.context, Constants.rAltAsIsoL3ShiftTag diff --git a/bVNC/src/main/res/layout-large/canvas.xml b/bVNC/src/main/res/layout-large/canvas.xml index 85a1c826c..6a76f4a3a 100644 --- a/bVNC/src/main/res/layout-large/canvas.xml +++ b/bVNC/src/main/res/layout-large/canvas.xml @@ -10,16 +10,27 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="bottom|center" - android:background="@android:color/background_dark"> + android:background="@android:color/background_dark"> + + android:background="?attr/colorPrimary"> + - + + - - - - - - - - - + android:background="@android:color/background_dark"> + + android:background="?attr/colorPrimary"> + - + + - - - - - - -