From 0a72437088b3e8387aa6ab77e20293bc2385788a Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 16 Feb 2020 22:18:38 +0100 Subject: update to sdk 29, migrate to androidx, new GUI --- app/src/main/AndroidManifest.xml | 10 +- app/src/main/cpp/native-lib.cpp | 50 ++++- .../com/javispedro/vndroid/ControlService.java | 3 +- .../com/javispedro/vndroid/KeyEventOutput.java | 3 +- .../java/com/javispedro/vndroid/RFBServer.java | 15 +- .../java/com/javispedro/vndroid/ScreenGrabber.java | 3 +- .../javispedro/vndroid/ScreenMirrorGrabber.java | 36 +++- .../vndroid/ServerRunningNotification.java | 12 +- .../java/com/javispedro/vndroid/ServerService.java | 230 ++++++++++++++------- .../com/javispedro/vndroid/SettingsActivity.java | 213 +++++++++++++++++++ .../java/com/javispedro/vndroid/SetupActivity.java | 78 ------- app/src/main/res/layout/activity_setup.xml | 45 ---- app/src/main/res/layout/settings_activity.xml | 9 + app/src/main/res/values/arrays.xml | 3 + app/src/main/res/values/colors.xml | 6 +- app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 13 ++ app/src/main/res/values/styles.xml | 9 + app/src/main/res/xml/root_preferences.xml | 31 +++ 19 files changed, 543 insertions(+), 228 deletions(-) create mode 100644 app/src/main/java/com/javispedro/vndroid/SettingsActivity.java delete mode 100644 app/src/main/java/com/javispedro/vndroid/SetupActivity.java delete mode 100644 app/src/main/res/layout/activity_setup.xml create mode 100644 app/src/main/res/layout/settings_activity.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/xml/root_preferences.xml (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5786f0f..2ad6c82 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,7 +12,10 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + @@ -22,8 +25,8 @@ - + android:exported="true" + android:foregroundServiceType="mediaProjection" /> @@ -35,7 +38,6 @@ android:name="android.accessibilityservice" android:resource="@xml/controlservice" /> - \ No newline at end of file diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index 3a1811c..6216181 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -41,6 +41,7 @@ struct Data jobject cb_obj; jmethodID cb_ev_ptr; jmethodID cb_ev_key; + jmethodID cb_ev_client; std::atomic pendingPtrEvent; int ptrButtonMask, ptrX, ptrY; @@ -48,6 +49,10 @@ struct Data std::atomic pendingKeyEvent; rfbKeySym key; bool keyState; + + std::atomic pendingClientEvent; + + unsigned int numClients; }; static inline Data * getData(JNIEnv *env, jobject instance) @@ -71,6 +76,16 @@ static inline Data * getData(rfbClientPtr client) return data; } +static void listen_thread_client_gone(rfbClientPtr client) +{ + Data *data = getData(client); + + data->numClients--; + data->pendingClientEvent = true; + + eventfd_write(data->eventEventFd, 1); +} + static void listen_thread_main(Data *data, JavaVM *vm) { rfbScreenInfoPtr screen = data->screen; @@ -104,11 +119,20 @@ static void listen_thread_main(Data *data, JavaVM *vm) else if (data->listenEventFd >= 0 && FD_ISSET(data->listenEventFd, &listen_fds)) break; - rfbClientPtr cl = NULL; - if(client_fd >= 0) - cl = rfbNewClient(screen,client_fd); - if (cl && !cl->onHold ) - rfbStartOnHoldClient(cl); + if(client_fd >= 0) { + rfbClientPtr cl = rfbNewClient(screen, client_fd); + if (cl) { + cl->clientGoneHook = listen_thread_client_gone; + + data->numClients++; + data->pendingClientEvent = true; + + eventfd_write(data->eventEventFd, 1); + + if (!cl->onHold) + rfbStartOnHoldClient(cl); + } + } } } @@ -134,6 +158,11 @@ static void event_thread_main(Data *data, JavaVM *vm) env->CallVoidMethod(data->cb_obj, data->cb_ev_key, (jint)data->key, (jboolean)data->keyState); data->pendingKeyEvent = false; } + + if (data->pendingClientEvent) { + env->CallVoidMethod(data->cb_obj, data->cb_ev_client); + data->pendingClientEvent = false; + } } vm->DetachCurrentThread(); @@ -150,6 +179,7 @@ static void ptr_event(int buttonMask, int x, int y, rfbClientPtr cl) eventfd_write(data->eventEventFd, 1); + // Still send the event to RFB, to do server-side cursor processing rfbDefaultPtrAddEvent(buttonMask, x, y, cl); } @@ -199,10 +229,13 @@ Java_com_javispedro_vndroid_RFBServer_allocate(JNIEnv *env, jobject instance) assert(data->cb_ev_ptr); data->cb_ev_key = env->GetMethodID(cb_cls, "onKeyEvent", "(IZ)V"); assert(data->cb_ev_key); + data->cb_ev_client = env->GetMethodID(cb_cls, "onClientEvent", "()V"); + assert(data->cb_ev_client); data->screen = 0; data->listenEventFd = -1; data->eventEventFd = -1; + data->numClients = 0; env->SetLongField(instance, fid, reinterpret_cast(data)); @@ -359,3 +392,10 @@ Java_com_javispedro_vndroid_RFBServer_put_1image(JNIEnv *env, jobject instance, return JNI_TRUE; } + +extern "C" +JNIEXPORT jint JNICALL +Java_com_javispedro_vndroid_RFBServer_get_1num_1clients(JNIEnv *env, jobject instance) { + Data *data = getData(env, instance); + return data->numClients; +} \ No newline at end of file diff --git a/app/src/main/java/com/javispedro/vndroid/ControlService.java b/app/src/main/java/com/javispedro/vndroid/ControlService.java index db5362d..166e379 100644 --- a/app/src/main/java/com/javispedro/vndroid/ControlService.java +++ b/app/src/main/java/com/javispedro/vndroid/ControlService.java @@ -2,10 +2,11 @@ package com.javispedro.vndroid; import android.accessibilityservice.AccessibilityService; import android.content.Intent; -import android.support.annotation.Nullable; import android.util.Log; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.Nullable; + public class ControlService extends AccessibilityService { private final String TAG = ControlService.class.getSimpleName(); diff --git a/app/src/main/java/com/javispedro/vndroid/KeyEventOutput.java b/app/src/main/java/com/javispedro/vndroid/KeyEventOutput.java index 597942a..3ec4319 100644 --- a/app/src/main/java/com/javispedro/vndroid/KeyEventOutput.java +++ b/app/src/main/java/com/javispedro/vndroid/KeyEventOutput.java @@ -1,10 +1,11 @@ package com.javispedro.vndroid; import android.os.Bundle; -import android.support.annotation.Nullable; import android.util.Log; import android.view.accessibility.AccessibilityNodeInfo; +import androidx.annotation.Nullable; + import com.javispedro.vndroid.keymaps.KeyActionHandler; import com.javispedro.vndroid.keymaps.KeyHandler; diff --git a/app/src/main/java/com/javispedro/vndroid/RFBServer.java b/app/src/main/java/com/javispedro/vndroid/RFBServer.java index f5af05c..8838818 100644 --- a/app/src/main/java/com/javispedro/vndroid/RFBServer.java +++ b/app/src/main/java/com/javispedro/vndroid/RFBServer.java @@ -2,8 +2,8 @@ package com.javispedro.vndroid; import android.graphics.PixelFormat; import android.media.Image; -import android.support.annotation.Nullable; -import android.util.EventLog; + +import androidx.annotation.Nullable; import java.nio.ByteBuffer; @@ -25,6 +25,7 @@ public class RFBServer { public interface EventCallback { void onPointerEvent(int buttonMask, int x, int y); void onKeyEvent(int key, boolean state); + void onClientEvent(); } public RFBServer() { @@ -40,13 +41,13 @@ public class RFBServer { } public void stop() { - forgetLastImage(); shutdown(); + forgetLastImage(); } public void finalize() { - forgetLastImage(); shutdown(); + forgetLastImage(); deallocate(); } @@ -77,6 +78,10 @@ public class RFBServer { lastImage = image; } + public int getNumClients() { + return get_num_clients(); + } + private void forgetLastImage() { if (lastImage != null) { lastImage.close(); @@ -95,4 +100,6 @@ public class RFBServer { private native void set_event_callback(EventCallback c); private native boolean put_image(int width, int height, ByteBuffer buffer, int pixel_stride, int row_stride); + + private native int get_num_clients(); } diff --git a/app/src/main/java/com/javispedro/vndroid/ScreenGrabber.java b/app/src/main/java/com/javispedro/vndroid/ScreenGrabber.java index 54d11bc..a5701b3 100644 --- a/app/src/main/java/com/javispedro/vndroid/ScreenGrabber.java +++ b/app/src/main/java/com/javispedro/vndroid/ScreenGrabber.java @@ -5,9 +5,10 @@ import android.content.ContextWrapper; import android.hardware.display.VirtualDisplay; import android.media.Image; import android.media.ImageReader; -import android.support.annotation.Nullable; import android.util.Log; +import androidx.annotation.Nullable; + public abstract class ScreenGrabber extends ContextWrapper { private static final String TAG = ScreenGrabber.class.getSimpleName(); diff --git a/app/src/main/java/com/javispedro/vndroid/ScreenMirrorGrabber.java b/app/src/main/java/com/javispedro/vndroid/ScreenMirrorGrabber.java index 9f78b3c..c223c65 100644 --- a/app/src/main/java/com/javispedro/vndroid/ScreenMirrorGrabber.java +++ b/app/src/main/java/com/javispedro/vndroid/ScreenMirrorGrabber.java @@ -1,34 +1,50 @@ package com.javispedro.vndroid; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.media.ImageReader; import android.media.projection.MediaProjection; +import android.media.projection.MediaProjectionManager; import android.os.Handler; -import android.support.annotation.NonNull; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; +import android.view.WindowManager; public class ScreenMirrorGrabber extends ScreenGrabber { private static final String TAG = ScreenMirrorGrabber.class.getSimpleName(); - protected final MediaProjection projection; - protected final Display realDisplay; + protected MediaProjection projection; - protected final DisplayMetrics realDisplayMetrics; + protected int projAskCode; + protected Intent projAskData; + + protected Display realDisplay; + protected DisplayMetrics realDisplayMetrics; private float scale = 0.5f; - public ScreenMirrorGrabber(Context context, @NonNull MediaProjection proj, @NonNull Display display) { + public ScreenMirrorGrabber(Context context, MediaProjection proj) { super(context); projection = proj; - realDisplay = display; - realDisplayMetrics = new DisplayMetrics(); + projAskCode = 0; + projAskData = null; // Already obtained + } + + public ScreenMirrorGrabber(Context context, int projectionResultCode, Intent projectionResultData) { + super(context); + projection = null; + projAskCode = projectionResultCode; + projAskData = projectionResultData; } private void initDisplay() { + WindowManager wm = getSystemService(WindowManager.class); + realDisplay = wm.getDefaultDisplay(); + realDisplayMetrics = new DisplayMetrics(); realDisplay.getRealMetrics(realDisplayMetrics); Log.d(TAG, "real display size: " + realDisplayMetrics.widthPixels + "x" + realDisplayMetrics.heightPixels); @@ -55,6 +71,12 @@ public class ScreenMirrorGrabber extends ScreenGrabber { Log.w(TAG, "already started"); return; } + if (projection == null && projAskCode == Activity.RESULT_OK) { + MediaProjectionManager manager = getSystemService(MediaProjectionManager.class); + projection = manager.getMediaProjection(projAskCode, projAskData); + projAskCode = 0; + projAskData = null; + } initDisplay(); } diff --git a/app/src/main/java/com/javispedro/vndroid/ServerRunningNotification.java b/app/src/main/java/com/javispedro/vndroid/ServerRunningNotification.java index 6fcf8ab..224df0e 100644 --- a/app/src/main/java/com/javispedro/vndroid/ServerRunningNotification.java +++ b/app/src/main/java/com/javispedro/vndroid/ServerRunningNotification.java @@ -1,6 +1,5 @@ package com.javispedro.vndroid; -import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -8,11 +7,8 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Build; -import android.support.v4.app.NotificationCompat; + +import androidx.core.app.NotificationCompat; /** * Helper class for showing and canceling server running @@ -86,7 +82,7 @@ public class ServerRunningNotification { PendingIntent.getActivity( context, 0, - new Intent(context, SetupActivity.class), + new Intent(context, SettingsActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)) // Example additional actions for this notification. These will @@ -100,7 +96,7 @@ public class ServerRunningNotification { PendingIntent.getForegroundService( context, 0, - new Intent(context, ServerService.class).setAction(ServerService.ACTION_STOP), + new Intent(context, ServerService.class).setAction(ServerService.ACTION_STOP_SERVER), PendingIntent.FLAG_UPDATE_CURRENT)); return builder.build(); diff --git a/app/src/main/java/com/javispedro/vndroid/ServerService.java b/app/src/main/java/com/javispedro/vndroid/ServerService.java index bf72d21..96420df 100644 --- a/app/src/main/java/com/javispedro/vndroid/ServerService.java +++ b/app/src/main/java/com/javispedro/vndroid/ServerService.java @@ -1,61 +1,80 @@ package com.javispedro.vndroid; -import android.app.Activity; import android.app.Notification; import android.app.Service; import android.content.Intent; import android.content.res.Configuration; import android.media.Image; -import android.media.projection.MediaProjection; -import android.media.projection.MediaProjectionManager; +import android.os.Binder; import android.os.IBinder; import android.util.Log; -import android.view.WindowManager; import android.widget.Toast; import com.javispedro.vndroid.keymaps.AndroidKeyHandler; import com.javispedro.vndroid.keymaps.SpanishKeyHandler; +import java.lang.ref.WeakReference; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + public class ServerService extends Service { private static final String TAG = ServerService.class.getSimpleName(); - public static final String ACTION_START = "ACTION_START"; - public static final String ACTION_STOP = "ACTION_STOP"; - public static final String ACTION_NOTIFY_MEDIA_PROJECTION_RESULT = "ACTION_NOTIFY_MEDIA_PROJECTION_RESULT"; + + public static final String ACTION_START_SERVER = "ACTION_START_SERVER"; + public static final String ACTION_STOP_SERVER = "ACTION_STOP_SERVER"; + + public class ServerBinder extends Binder { + ServerService getService() { + return ServerService.this; + } + } + public interface ServerStatusCallback { + void onServerStatusChanged(); + void onNumClientChanged(); + } + + private ServerBinder binder; + private WeakReference callback; private ScreenGrabber screenGrabber; private RFBServer rfbServer; private PointerEventOutput pointerOut; private KeyEventOutput keyOut; - public ServerService() { - } - @Override public void onCreate() { + Log.d(TAG, "onCreate"); + binder = new ServerBinder(); ServerRunningNotification.initNotificationChannel(this); System.loadLibrary("native-lib"); } + @Override + public void onDestroy() { + Log.d(TAG, "onDestroy"); + cleanupServer(); + binder = null; + callback = null; + } + @Override public IBinder onBind(Intent intent) { - throw new UnsupportedOperationException("Not implemented"); + return binder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null) { - switch (intent.getAction()) { - case ACTION_START: - start(); - break; - case ACTION_STOP: - stop(); - break; - case ACTION_NOTIFY_MEDIA_PROJECTION_RESULT: - notifyMediaProjectionResult(intent.getIntExtra("resultCode", Activity.RESULT_CANCELED), - (Intent) intent.getParcelableExtra("resultData")); - break; - } + Log.d(TAG, "onStartCommand intent=" + intent); + switch (intent.getAction()) { + case ACTION_START_SERVER: + startServer(); + return START_REDELIVER_INTENT; + case ACTION_STOP_SERVER: + stopServer(); + return START_NOT_STICKY; } return super.onStartCommand(intent, flags, startId); } @@ -69,43 +88,15 @@ public class ServerService extends Service { } } - protected class ScreenGrabberCallback implements ScreenGrabber.Callback { - @Override - public void onImage(Image image) { - if (rfbServer != null) { - rfbServer.putImage(image); - } else { - image.close(); - } + public void setServerStatusCallback(ServerStatusCallback callback) { + if (callback != null) { + this.callback = new WeakReference<>(callback); + } else { + this.callback = null; } } - protected class EventCallback implements RFBServer.EventCallback { - @Override - public void onPointerEvent(int buttonMask, int x, int y) { - try { - x = screenGrabber.scaleInputX(x); - y = screenGrabber.scaleInputY(y); - pointerOut.postPointerEvent((byte) buttonMask, x, y); - } catch (Exception e) { - Log.e(TAG, "Exception on pointer EventCallback: " + e.toString()); - e.printStackTrace(); - // Need to supress the exception, otherwise we'll crash JNI - } - } - - @Override - public void onKeyEvent(int key, boolean state) { - try { - keyOut.postKeyEvent(key, state); - } catch (Exception e) { - Log.e(TAG, "Exception on key EventCallback: " + e.toString()); - e.printStackTrace(); - } - } - } - - protected void start() { + public void startServer() { if (rfbServer != null) { Log.w(TAG, "cannot start, already started"); return; @@ -113,6 +104,9 @@ public class ServerService extends Service { Log.d(TAG, "starting"); + Notification notification = ServerRunningNotification.build(this); + startForeground(1, notification); + if (!ControlService.isServiceStarted()) { Toast toast = Toast.makeText(this, R.string.toast_no_input_service, Toast.LENGTH_SHORT); toast.show(); @@ -123,7 +117,6 @@ public class ServerService extends Service { keyOut.addHandler(new AndroidKeyHandler()); keyOut.addHandler(new SpanishKeyHandler()); - if (screenGrabber == null) { screenGrabber = new ScreenVirtualGrabber(this); } @@ -135,13 +128,61 @@ public class ServerService extends Service { screenGrabber.setCallback(new ScreenGrabberCallback()); screenGrabber.start(); - Notification notification = ServerRunningNotification.build(this); - startForeground(1, notification); + notifyServerStatusChanged(); } - protected void stop() { + public void stopServer() { Log.d(TAG, "stopping"); + cleanupServer(); + + stopForeground(true); + stopSelf(); + + notifyServerStatusChanged(); + } + + public boolean isServerActive() { + return rfbServer != null; + } + + public void setMediaProjectionResult(int resultCode, Intent data) { + if (screenGrabber != null) { + Log.w(TAG, "already have an screen grabber"); + } + + screenGrabber = new ScreenMirrorGrabber(this, resultCode, data); + } + + public int getListeningDisplay() { + return 0; + } + + public List getListeningIPAddresses() { + LinkedList result = new LinkedList(); + try { + for (Enumeration netif_it = NetworkInterface.getNetworkInterfaces(); netif_it.hasMoreElements(); ) { + NetworkInterface netif = netif_it.nextElement(); + + for (Enumeration addr_it = netif.getInetAddresses(); addr_it.hasMoreElements(); ) { + InetAddress addr = addr_it.nextElement(); + + if (addr.isLoopbackAddress()) continue; + + result.add(addr.getHostAddress()); + } + } + } catch (Exception ex) { + Log.w(TAG, "While enumerating IP addresses: " + ex.toString()); + } + return result; + } + + public int getNumClients() { + return rfbServer.getNumClients(); + } + + private void cleanupServer() { if (rfbServer != null) { rfbServer.stop(); rfbServer = null; @@ -156,20 +197,67 @@ public class ServerService extends Service { if (keyOut != null) { keyOut = null; } + } - stopForeground(true); - stopSelf(); + private ServerStatusCallback getStatusCallback() { + return callback == null ? null : callback.get(); } - protected void notifyMediaProjectionResult(int resultCode, Intent data) { - if (screenGrabber != null) { - Log.w(TAG, "already have an screen grabber"); + private void notifyServerStatusChanged() { + ServerStatusCallback cb = getStatusCallback(); + if (cb == null) return; + cb.onServerStatusChanged(); + } + + private void notifyNumClientChanged() { + ServerStatusCallback cb = getStatusCallback(); + if (cb == null) return; + cb.onNumClientChanged(); + } + + private class ScreenGrabberCallback implements ScreenGrabber.Callback { + @Override + public void onImage(Image image) { + if (rfbServer != null) { + rfbServer.putImage(image); + } else { + image.close(); + } } - MediaProjectionManager manager = getSystemService(MediaProjectionManager.class); - MediaProjection projection = manager.getMediaProjection(resultCode, data); + } - WindowManager wm = getSystemService(WindowManager.class); + private class EventCallback implements RFBServer.EventCallback { + @Override + public void onPointerEvent(int buttonMask, int x, int y) { + try { + x = screenGrabber.scaleInputX(x); + y = screenGrabber.scaleInputY(y); + pointerOut.postPointerEvent((byte) buttonMask, x, y); + } catch (Exception e) { + Log.e(TAG, "Exception on pointer EventCallback: " + e.toString()); + e.printStackTrace(); + // Need to suppress the exception, otherwise we'll crash JNI + } + } - screenGrabber = new ScreenMirrorGrabber(this, projection, wm.getDefaultDisplay()); + @Override + public void onKeyEvent(int key, boolean state) { + try { + keyOut.postKeyEvent(key, state); + } catch (Exception e) { + Log.e(TAG, "Exception on key EventCallback: " + e.toString()); + e.printStackTrace(); + } + } + + @Override + public void onClientEvent() { + try { + notifyNumClientChanged(); + } catch (Exception e) { + Log.e(TAG, "Exception on client EventCallback: " + e.toString()); + e.printStackTrace(); + } + } } } diff --git a/app/src/main/java/com/javispedro/vndroid/SettingsActivity.java b/app/src/main/java/com/javispedro/vndroid/SettingsActivity.java new file mode 100644 index 0000000..676af3d --- /dev/null +++ b/app/src/main/java/com/javispedro/vndroid/SettingsActivity.java @@ -0,0 +1,213 @@ +package com.javispedro.vndroid; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.media.projection.MediaProjectionManager; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.TwoStatePreference; + +import java.util.List; + +public class SettingsActivity extends AppCompatActivity { + private static final String TAG = SettingsActivity.class.getSimpleName(); + + private static final int REQUEST_MEDIA_PROJECTION = 1; + + private static final boolean mirrorScreenMode = true; + + + public static class SettingsFragment extends PreferenceFragmentCompat implements ServerService.ServerStatusCallback { + private ServerConnection serverConnection = null; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.root_preferences, rootKey); + findPreference(getString(R.string.settings_enable_key)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + TwoStatePreference pref = (TwoStatePreference) preference; + setServerEnabled(pref.isChecked()); + return true; + } + }); + } + + @Override + public void onStart() { + super.onStart(); + serverConnection = new ServerConnection(); + serverConnection.bind(requireActivity(), this); + } + + @Override + public void onStop() { + serverConnection.close(requireActivity()); + serverConnection = null; + super.onStop(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_MEDIA_PROJECTION: + notifyMediaProjectionResult(resultCode, data); + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onServerStatusChanged() { + postUpdateServerStatus(); + } + + @Override + public void onNumClientChanged() { + postUpdateServerStatus(); + } + + private void setServerEnabled(boolean state) { + Log.d(TAG, "setServerEnabled: " + state); + ServerService server = serverConnection.getServer(); + if (state) { + if (server == null) { + throw new IllegalStateException("ServerService not bound"); + } + if (server.isServerActive()) return; + + if (mirrorScreenMode) { + MediaProjectionManager manager = requireContext().getSystemService(MediaProjectionManager.class); + startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); + } else { + server.startServer(); + } + } else { + if (server == null) return; + server.stopServer(); + } + } + + private void notifyMediaProjectionResult(int resultCode, Intent resultData) { + if (resultCode != Activity.RESULT_OK) { + Log.w(TAG, "User cancelled media projection"); + Toast.makeText(requireContext(), getString(R.string.toast_no_mirror_permission), Toast.LENGTH_SHORT).show(); + updateServerStatus(); + return; + } + + ServerService server = serverConnection.getServer(); + if (server == null) { + Log.e(TAG, "server died before projection could be sent"); + updateServerStatus(); + return; + } + if (server.isServerActive()) { + Log.w(TAG, "server already active"); + updateServerStatus(); + return; + } + + server.setMediaProjectionResult(resultCode, resultData); + server.startServer(); + } + + private void updateServerStatus() { + TwoStatePreference enablePref = findPreference(getString(R.string.settings_enable_key)); + Preference statusPref = findPreference(getString(R.string.settings_status_key)); + ServerService server = serverConnection.getServer(); + if (server != null && server.isServerActive()) { + enablePref.setChecked(true); + StringBuilder sb = new StringBuilder(); + int display = server.getListeningDisplay(); + List ips = server.getListeningIPAddresses(); + if (!ips.isEmpty()) { + sb.append('\n'); + sb.append(getString(R.string.status_server_addresses)); + for (String ip : ips) { + sb.append(' '); + sb.append(ip); + sb.append(':'); + sb.append(display); + } + } + int numClients = server.getNumClients(); + if (numClients > 0) { + sb.append('\n'); + sb.append(getResources().getQuantityString(R.plurals.status_num_clients, numClients, numClients)); + } + statusPref.setSummary(sb.toString()); + } else { + enablePref.setChecked(false); + statusPref.setSummary(""); + } + } + + private void postUpdateServerStatus() { + requireActivity().runOnUiThread(new Runnable() { + public void run() { + updateServerStatus(); + } + }); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.settings_activity); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.settings, new SettingsFragment()) + .commit(); + } + + private static class ServerConnection implements ServiceConnection { + private ServerService server = null; + private ServerService.ServerStatusCallback callback = null; + + public void bind(Activity activity, ServerService.ServerStatusCallback callback) { + this.callback = callback; + activity.bindService(new Intent(activity, ServerService.class), this, Context.BIND_AUTO_CREATE); + } + + public void close(Activity activity) { + server.setServerStatusCallback(null); + activity.unbindService(this); + } + + public boolean connected() { + return server != null; + } + + public @Nullable ServerService getServer() { + return server; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + server = ((ServerService.ServerBinder) service).getService(); + server.setServerStatusCallback(this.callback); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + server = null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javispedro/vndroid/SetupActivity.java b/app/src/main/java/com/javispedro/vndroid/SetupActivity.java deleted file mode 100644 index 123bbe7..0000000 --- a/app/src/main/java/com/javispedro/vndroid/SetupActivity.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.javispedro.vndroid; - -import android.app.Activity; -import android.content.Intent; -import android.media.projection.MediaProjectionManager; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.View; - -public class SetupActivity extends AppCompatActivity { - private static String TAG = SetupActivity.class.getSimpleName(); - - private static final int REQUEST_MEDIA_PROJECTION = 1; - - private boolean mirror = true; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_setup); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) { - case REQUEST_MEDIA_PROJECTION: - if (resultCode != Activity.RESULT_OK) { - Log.w(TAG, "User cancelled media projection"); - return; - } - - notifyMediaProjectionResult(resultCode, data); - startServer(); - - break; - } - } - - private void startServer() - { - Intent intent = new Intent(this, ServerService.class); - intent.setAction(ServerService.ACTION_START); - startService(intent); - } - - private void stopServer() - { - Intent intent = new Intent(this, ServerService.class); - intent.setAction(ServerService.ACTION_STOP); - startService(intent); - } - - private void notifyMediaProjectionResult(int resultCode, Intent resultData) - { - Intent intent = new Intent(this, ServerService.class); - intent.setAction(ServerService.ACTION_NOTIFY_MEDIA_PROJECTION_RESULT); - intent.putExtra("resultCode", resultCode); - intent.putExtra("resultData", resultData); - startService(intent); - } - - public void onStartClick(View view) { - Log.d(TAG, "onStartClick"); - if (mirror) { - MediaProjectionManager manager = getSystemService(MediaProjectionManager.class); - startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); - } else { - startServer(); - } - } - - public void onStopClick(View view) { - Log.d(TAG, "onStopClick"); - stopServer(); - } -} diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml deleted file mode 100644 index 9512149..0000000 --- a/app/src/main/res/layout/activity_setup.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - -