Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intent interface for MainService #98

Merged
merged 56 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
78bb1d8
MainService: make client connect callbacks package-private
bk138 Jun 6, 2023
739ccb8
MainService: make more internal static functions package-private
bk138 Jun 6, 2023
4801396
MainService: make getStatusEventStream() package-private
bk138 Jun 12, 2023
5bcb720
MainService: move vncStartServer() to intent handling
bk138 Jun 8, 2023
f9dfc24
native: add vncIsActive()
bk138 Jun 8, 2023
ec57ac8
MainService: expose vncIsActive()
bk138 Jun 8, 2023
9e64a71
MainService: stop if no known action was given
bk138 Jun 8, 2023
b1ff2a6
MainActivity: show password on input focus
bk138 Jun 9, 2023
9a9cc1f
Defaults,Constants: provide an access key
bk138 Jun 9, 2023
382b39a
MainActivity: wire up access key setting UI
bk138 Jun 9, 2023
a18bf18
MainService: export and require access key
bk138 Jun 9, 2023
239b0e9
MainActivity: provide access key
bk138 Jun 9, 2023
ce6a9f2
InputRequestActivity: provide access key
bk138 Jun 9, 2023
05a4f38
MediaProjectionRequestActivity: provide access key
bk138 Jun 9, 2023
cb46a89
OnBootReceiver: provide access key
bk138 Jun 9, 2023
4aa4f22
WriteStorageRequestActivity: provide access key
bk138 Jun 9, 2023
c2a7193
MainService,MainActivity: replace static connectReverse() w/ Intent
bk138 Jun 12, 2023
ea759ad
MainService: get password and port from Intent extras
bk138 Jun 12, 2023
fbfa68e
MainActivity: provide port and password w/ start Intent
bk138 Jun 12, 2023
d925119
InputService: rename isEnabled() to isConnected()
bk138 Jun 12, 2023
89e91a0
MainActivity: fix password and access key entry
bk138 Jun 12, 2023
4b59bc1
Defaults, README: add new viewOnly default
bk138 Jun 12, 2023
3602398
Constants: add view-only setting key
bk138 Jun 12, 2023
e424d43
InputService: add means to enable/disable while connected
bk138 Jun 12, 2023
655f863
MainService,InputRequestActivity: don't enable input if EXTRA_VIEW_ON…
bk138 Jun 12, 2023
fb3c325
MainActivity: wire up view-only setting
bk138 Jun 12, 2023
43a2da7
Constants: add keys for persisting runtime port and password
bk138 Jun 12, 2023
85066d5
MainService: go back to call vncStartServer() from redelivered intents
bk138 Jun 12, 2023
ce1c9db
Constants: add pref keys for persisting last scaling and input
bk138 Jun 12, 2023
9783f38
MainActivity: add scaling user pref to start intent
bk138 Jun 12, 2023
d2a1b36
InputService,MainService: rework input enabling && scaling setting
bk138 Jun 12, 2023
d711ddb
Constants: add file transfer keys
bk138 Jun 12, 2023
49ee711
Defaults: add file transfer defaults
bk138 Jun 12, 2023
46af73f
MainService: wire up EXTRA_FILE_TRANSFER
bk138 Jun 12, 2023
772eff8
WriteStorageRequestActivity: read EXTRA_FILE_TRANSFER
bk138 Jun 12, 2023
6218f25
MainActivity: wire up file transfer setting
bk138 Jun 12, 2023
5f5556f
MainActivity: adapt UI to disable listening
bk138 Jun 12, 2023
77455d7
MainService: make ACTION_STOP public
bk138 Jun 12, 2023
bf85388
MainService,MainActivity: replace static connectRepeater() w/ Intent
bk138 Jun 12, 2023
6b4518d
MainService: fix lints
bk138 Jun 12, 2023
1d83a57
MainService: send result of ACTION_STOP
bk138 Jun 12, 2023
bb20999
MainActivity: show port hint instead of -1
bk138 Jun 12, 2023
38de484
MainActivity: add shorter wording
bk138 Jun 12, 2023
2127172
MainService: don't leak request args in broadcast answers
bk138 Jun 12, 2023
e3b08b1
MainActivity: save request extras locally
bk138 Jun 12, 2023
d6fdc80
MainService: broadcast results for ACTION_START
bk138 Jun 12, 2023
9b12a55
Constants,Defaults: make key private
bk138 Jun 12, 2023
bfc4d7e
MainService: let ACTION_START, ACTION_STOP return whether they succeeded
bk138 Jun 13, 2023
4217c14
Constants,MainActivity: make keys private
bk138 Jun 13, 2023
eb8741f
Constants,MainService: make pref keys private
bk138 Jun 13, 2023
d5f71a8
MainService: let getIPv4sAndPorts() return last used port
bk138 Jun 13, 2023
5eb73f4
OnBootReceiver: provide user prefs to MainService
bk138 Jun 13, 2023
41f0dca
README: update example defaults.json
bk138 Jun 13, 2023
5cc8133
README: document intent remote control
bk138 Jun 13, 2023
0a9c93a
Defaults: set fileTransfer to what it is for older versions
bk138 Jun 13, 2023
e76521a
MainService: use user settings for values not provided in EXTRA_START
bk138 Jun 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,91 @@ An example `defaults.json` with completely new defaults (not all entries need to
"portReverse": 5555,
"portRepeater": 5556,
"scaling": 0.7,
"password": "supersecure"
"viewOnly": false,
"fileTranfer": true,
"password": "supersecure",
"accessKey": "evenmoresecure"
}
```

### Remote Control via the Intent Interface

droidVNC-NG features a remote control interface by means of Intents. This allows starting the VNC
server from other apps or on certain events. It is designed to be working with automation apps
like [MacroDroid](https://www.macrodroid.com/), [Automate](https://llamalab.com/automate/) or
[Tasker](https://tasker.joaoapps.com/) as well as to be called from code.

You basically send an explicit Intent to `net.christianbeier.droidvnc_ng.MainService` with one of
the following Actions and associated Extras set:

* `net.christianbeier.droidvnc_ng.ACTION_START`: Starts the server.
* `net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY`: Required String Extra containing the remote control interface's access key. You can get/set this from the Admin Panel.
* `net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID`: Optional String Extra containing a unique id for this request. Used to identify the answer from the service.
* `net.christianbeier.droidvnc_ng.EXTRA_PORT`: Optional Integer Extra setting the listening port. Set to `-1` to disable listening.
* `net.christianbeier.droidvnc_ng.EXTRA_PASSWORD`: Optional String Extra containing VNC password.
* `net.christianbeier.droidvnc_ng.EXTRA_SCALING`: Optional Float Extra between 0.0 and 1.0 describing the server-side framebuffer scaling.
* `net.christianbeier.droidvnc_ng.EXTRA_VIEW_ONLY`: Optional Boolean Extra toggling view-only mode.
* `net.christianbeier.droidvnc_ng.EXTRA_FILE_TRANSFER`: Optional Boolean Extra toggling the file transfer feature.

* `net.christianbeier.droidvnc_ng.ACTION_CONNECT_REVERSE`: Make an outbound connection to a listening viewer.
* `net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY`: Required String Extra containing the remote control interface's access key. You can get/set this from the Admin Panel.
* `net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID`: Optional String Extra containing a unique id for this request. Used to identify the answer from the service.
* `net.christianbeier.droidvnc_ng.EXTRA_HOST`: Required String Extra setting the host to connect to.
* `net.christianbeier.droidvnc_ng.EXTRA_PORT`: Optional Integer Extra setting the remote port.

* `net.christianbeier.droidvnc_ng.ACTION_CONNECT_REPEATER` Make an outbound connection to a repeater.
* `net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY`: Required String Extra containing the remote control interface's access key. You can get/set this from the Admin Panel.
* `net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID`: Optional String Extra containing a unique id for this request. Used to identify the answer from the service.
* `net.christianbeier.droidvnc_ng.EXTRA_HOST`: Required String Extra setting the host to connect to.
* `net.christianbeier.droidvnc_ng.EXTRA_PORT`: Optional Integer Extra setting the remote port.
* `net.christianbeier.droidvnc_ng.EXTRA_REPEATER_ID`: Required String Extra setting the ID on the repeater.

* `net.christianbeier.droidvnc_ng.ACTION_STOP`: Stops the server.
* `net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY`: Required String Extra containing the remote control interface's access key. You can get/set this from the Admin Panel.
* `net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID`: Optional String Extra containing a unique id for this request. Used to identify the answer from the service.

The service answers with a Broadcast Intent with its Action mirroring your request:

* Action: one of the above Actions you requested
* `net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID`: The request id this answer is for.
* `net.christianbeier.droidvnc_ng.EXTRA_REQUEST_SUCCESS`: Boolean Extra describing the outcome of the request.

#### Examples

##### Start a password-protected view-only server on port 5901

Using `adb shell am` syntax:

```shell
adb shell am start-foreground-service \
-n net.christianbeier.droidvnc_ng/.MainService \
-a net.christianbeier.droidvnc_ng.ACTION_START \
--es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY de32550a6efb43f8a5d145e6c07b2cde \
--es net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID abc123 \
--ei net.christianbeier.droidvnc_ng.EXTRA_PORT 5901 \
--es net.christianbeier.droidvnc_ng.EXTRA_PASSWORD supersecure \
--ez net.christianbeier.droidvnc_ng.EXTRA_VIEW_ONLY true
```

##### Make an outbound connection to a listening viewer from the running server

For example from Java code:

See [MainActivity.java](app/src/main/java/net/christianbeier/droidvnc_ng/MainActivity.java).

##### Stop the server again

Using `adb shell am` syntax again:

```shell
adb shell am start-foreground-service \
-n net.christianbeier.droidvnc_ng/.MainService \
-a net.christianbeier.droidvnc_ng.ACTION_STOP \
--es net.christianbeier.droidvnc_ng.EXTRA_ACCESS_KEY de32550a6efb43f8a5d145e6c07b2cde \
--es net.christianbeier.droidvnc_ng.EXTRA_REQUEST_ID def456
```


## Notes

* Requires at least Android 7.
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@
</intent-filter>
</receiver>

<!--
We export the MainService without a permission to enable 3rd party apps to send
send Intents to it (otherwise they would have to know about the permission at build
time). Access is secured differently by an access key Intent senders have to supply.
As only explicit Intents are handled (i.e. no intent-filter), this is secure.
-->
<service
android:name=".MainService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="mediaProjection" />

<service
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/cpp/droidvnc-ng.c
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,7 @@ JNIEXPORT jint JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncGetFr

return theScreen->height;
}

JNIEXPORT jboolean JNICALL Java_net_christianbeier_droidvnc_1ng_MainService_vncIsActive(JNIEnv *env, jobject thiz) {
return theScreen && rfbIsActive(theScreen);
}
15 changes: 12 additions & 3 deletions app/src/main/java/net/christianbeier/droidvnc_ng/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@
package net.christianbeier.droidvnc_ng;

public class Constants {
/*
user settings
*/
public static final String PREFS_KEY_SETTINGS_PORT = "settings_port";
public static final String PREFS_KEY_SETTINGS_PASSWORD = "settings_password" ;
public static final String PREFS_KEY_SETTINGS_START_ON_BOOT = "settings_start_on_boot" ;
public static final String PREFS_KEY_SETTINGS_SCALING = "settings_scaling" ;
public static final String PREFS_KEY_REVERSE_VNC_LAST_HOST = "reverse_vnc_last_host" ;
public static final String PREFS_KEY_REPEATER_VNC_LAST_HOST = "repeater_vnc_last_host" ;
public static final String PREFS_KEY_REPEATER_VNC_LAST_ID = "repeater_vnc_last_id" ;
public static final String PREFS_KEY_SETTINGS_VIEW_ONLY = "settings_view_only" ;
public static final String PREFS_KEY_SETTINGS_ACCESS_KEY = "settings_access_key";
public static final String PREFS_KEY_SETTINGS_FILE_TRANSFER = "settings_file_transfer";

/*
persisted runtime values shared between components
*/
public static final String PREFS_KEY_SERVER_LAST_SCALING = "server_last_scaling" ;
public static final String PREFS_KEY_INPUT_LAST_ENABLED = "input_last_enabled" ;
}
39 changes: 38 additions & 1 deletion app/src/main/java/net/christianbeier/droidvnc_ng/Defaults.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,21 @@
package net.christianbeier.droidvnc_ng

import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import java.io.File
import androidx.preference.PreferenceManager
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.io.File
import java.util.UUID


@OptIn(ExperimentalSerializationApi::class)
@Serializable
class Defaults {
companion object {
private const val TAG = "Defaults"
private const val PREFS_KEY_DEFAULTS_ACCESS_KEY = "defaults_access_key"
}

@EncodeDefault
Expand All @@ -51,23 +55,56 @@ class Defaults {
var scaling = 1.0f
private set

@EncodeDefault
var viewOnly = false
private set

@EncodeDefault
var fileTranfer = true
private set

@EncodeDefault
var password = ""
private set

@EncodeDefault
var accessKey = ""
private set
/*
NB if adding fields here, don't forget to add their copying in the constructor as well!
*/

constructor(context: Context) {
/*
persist randomly generated defaults
*/
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val defaultAccessKey = prefs.getString(PREFS_KEY_DEFAULTS_ACCESS_KEY, null)
if (defaultAccessKey == null) {
val ed: SharedPreferences.Editor = prefs.edit()
ed.putString(
PREFS_KEY_DEFAULTS_ACCESS_KEY,
UUID.randomUUID().toString().replace("-".toRegex(), "")
)
ed.apply()
}
this.accessKey = prefs.getString(PREFS_KEY_DEFAULTS_ACCESS_KEY, null)!!

/*
read provided defaults
*/
val jsonFile = File(context.getExternalFilesDir(null), "defaults.json")
try {
val jsonString = jsonFile.readText()
val readDefault = Json.decodeFromString<Defaults>(jsonString)
this.port = readDefault.port
this.portReverse = readDefault.portReverse
this.portRepeater = readDefault.portRepeater
this.fileTranfer = readDefault.fileTranfer
this.scaling = readDefault.scaling
this.viewOnly = readDefault.viewOnly
this.password = readDefault.password
this.accessKey = readDefault.accessKey
// add here!
} catch (e: Exception) {
Log.w(TAG, "${e.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;

public class InputRequestActivity extends AppCompatActivity {

Expand All @@ -39,7 +40,13 @@ public class InputRequestActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if(!InputService.isEnabled()) {
// if VIEW_ONLY is set, bail out early without bothering the user
if(getIntent().getBooleanExtra(MainService.EXTRA_VIEW_ONLY, new Defaults(this).getViewOnly())) {
postResultAndFinish(false);
return;
}

if(!InputService.isConnected()) {
new AlertDialog.Builder(this)
.setCancelable(false)
.setTitle(R.string.input_a11y_title)
Expand Down Expand Up @@ -67,7 +74,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_INPUT) {
Log.d(TAG, "onActivityResult");
postResultAndFinish(InputService.isEnabled());
postResultAndFinish(InputService.isConnected());
}
}

Expand All @@ -81,6 +88,7 @@ private void postResultAndFinish(boolean isA11yEnabled) {
Intent intent = new Intent(this, MainService.class);
intent.setAction(MainService.ACTION_HANDLE_INPUT_RESULT);
intent.putExtra(MainService.EXTRA_INPUT_RESULT, isA11yEnabled);
intent.putExtra(MainService.EXTRA_ACCESS_KEY, PreferenceManager.getDefaultSharedPreferences(this).getString(Constants.PREFS_KEY_SETTINGS_ACCESS_KEY, new Defaults(this).getAccessKey()));
startService(intent);
finish();
}
Expand Down
44 changes: 25 additions & 19 deletions app/src/main/java/net/christianbeier/droidvnc_ng/InputService.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public synchronized void onCancelled(GestureDescription gestureDescription) {
private static final String TAG = "InputService";

private static InputService instance;
/**
* Scaling factor that's applied to incoming pointer events by dividing coordinates by
* the given factor.
*/
static float scaling;
static boolean isEnabled;

private Handler mMainHandler;

Expand All @@ -64,7 +70,6 @@ public synchronized void onCancelled(GestureDescription gestureDescription) {
private boolean mIsKeyDelDown;
private boolean mIsKeyEscDown;

private float mScaling;

private final GestureCallback mGestureCallback = new GestureCallback();

Expand All @@ -80,8 +85,9 @@ public void onServiceConnected()
{
super.onServiceConnected();
instance = this;
isEnabled = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(Constants.PREFS_KEY_INPUT_LAST_ENABLED, !new Defaults(this).getViewOnly());
scaling = PreferenceManager.getDefaultSharedPreferences(this).getFloat(Constants.PREFS_KEY_SERVER_LAST_SCALING, new Defaults(this).getScaling());
mMainHandler = new Handler(instance.getMainLooper());
mScaling = PreferenceManager.getDefaultSharedPreferences(this).getFloat(Constants.PREFS_KEY_SETTINGS_SCALING, new Defaults(this).getScaling());
Log.i(TAG, "onServiceConnected");
}

Expand All @@ -92,32 +98,22 @@ public void onDestroy() {
Log.i(TAG, "onDestroy");
}

public static boolean isEnabled()
public static boolean isConnected()
{
return instance != null;
}

/**
* Set scaling factor that's applied to incoming pointer events by dividing coordinates by
* the given factor.
* @param scaling The scaling factor as a real number.
* @return Whether scaling was applied or not.
*/
public static boolean setScaling(float scaling) {
try {
instance.mScaling = scaling;
return true;
} catch (Exception e) {
return false;
}
}

@SuppressWarnings("unused")
public static void onPointerEvent(int buttonMask, int x, int y, long client) {

if(!isEnabled) {
return;
}

try {
x /= instance.mScaling;
y /= instance.mScaling;
x /= scaling;
y /= scaling;

/*
left mouse button
Expand Down Expand Up @@ -172,6 +168,11 @@ public static void onPointerEvent(int buttonMask, int x, int y, long client) {
}

public static void onKeyEvent(int down, long keysym, long client) {

if(!isEnabled) {
return;
}

Log.d(TAG, "onKeyEvent: keysym " + keysym + " down " + down + " by client " + client);

/*
Expand Down Expand Up @@ -235,6 +236,11 @@ public static void onKeyEvent(int down, long keysym, long client) {
}

public static void onCutText(String text, long client) {

if(!isEnabled) {
return;
}

Log.d(TAG, "onCutText: text '" + text + "' by client " + client);

try {
Expand Down
Loading