package com.javispedro.vndroid; import android.app.Notification; import android.app.Service; import android.content.Intent; import android.content.res.Configuration; import android.media.Image; import android.os.Binder; import android.os.IBinder; import android.util.Log; 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_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; @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) { return binder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { 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); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (screenGrabber != null && screenGrabber.hasSizeChanged()) { Log.d(TAG, "screen size has changed"); screenGrabber.updateScreenSize(); } } public void setServerStatusCallback(ServerStatusCallback callback) { if (callback != null) { this.callback = new WeakReference<>(callback); } else { this.callback = null; } } public void startServer() { if (rfbServer != null) { Log.w(TAG, "cannot start, already started"); return; } 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(); } pointerOut = new PointerEventOutput(); keyOut = new KeyEventOutput(); keyOut.addHandler(new AndroidKeyHandler()); keyOut.addHandler(new SpanishKeyHandler()); if (screenGrabber == null) { screenGrabber = new ScreenVirtualGrabber(this); } rfbServer = new RFBServer(); rfbServer.setEventCallback(new EventCallback()); rfbServer.start(); screenGrabber.setCallback(new ScreenGrabberCallback()); screenGrabber.start(); notifyServerStatusChanged(); } 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; } if (screenGrabber != null) { screenGrabber.stop(); screenGrabber = null; } if (pointerOut != null) { pointerOut = null; } if (keyOut != null) { keyOut = null; } } private ServerStatusCallback getStatusCallback() { return callback == null ? null : callback.get(); } 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(); } } } 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 } } @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(); } } } }