android: Allow freerdp mobile version to be launched from URI (freerdp://)
authorzihao.jiang <zihao.jiang@yahoo.com>
Sat, 27 Feb 2016 10:31:43 +0000 (18:31 +0800)
committerzihao.jiang <zihao.jiang@yahoo.com>
Tue, 8 Mar 2016 19:22:02 +0000 (03:22 +0800)
It would be good if we have a easy way to call aFreeRDP in another Android APP (Requirement also mentioned in #2720)
We can define a scheme (freerdp://) as unified way to launch FreeRDP from another APP or browser and connect to compatible RDP server
1. Define scheme freerdp://
2. General form could be freerdp://user@hostname:port/connect?key1=value&key2=-&key3=%2b&key4=
3. [user] part would be translated to /u:
4. [hostname:port] would be translated to /v:
5. The [user@hostname:port] part would be used as app title, currently it's just the progress dialog title
6. query parameters would be translated to command line arguments. Later same arguments will overwrite the formers:
    a. key1=value:  => /key1:value
    b. key2=-:  => -key2
    c. key3=%2b  => +key3 (%2b is url encoded +)
d. key4=  => /key4
e. Especially, drive=sdcard will be properly handled with local sdcard path. On my device it will be translated to /drive:sdcard,/storage/emulated/0

Owing to the refactor work in PR #3097, we now pass same command line argument to JNI for freerdp settings.
We just need to make the SessionActivity accept freerdp scheme and translate argument from URI form to command line form.

client/Android/Studio/freeRDPCore/src/main/AndroidManifest.xml
client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/GlobalApp.java
client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/application/SessionState.java
client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/presentation/SessionActivity.java
client/Android/Studio/freeRDPCore/src/main/java/com/freerdp/freerdpcore/services/LibFreeRDP.java

index 7860018..e50a4dc 100644 (file)
                                android:theme="@style/Theme.Main"
                                android:configChanges="orientation|keyboardHidden|keyboard"
                                android:windowSoftInputMode="adjustResize">
+                               <intent-filter>
+                                       <action android:name="android.intent.action.VIEW" />
+                                       <category android:name="android.intent.category.DEFAULT" />              
+                                       <category android:name="android.intent.category.BROWSABLE" />
+                                       <data android:scheme="freerdp" android:host="*" />
+                               </intent-filter>
                        </activity>
                        <activity android:name=".presentation.AboutActivity"
                                android:label="@string/title_about"
index ee78e99..e176eb0 100644 (file)
@@ -13,6 +13,7 @@ import android.app.Application;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.Uri;
 import android.util.Log;
 
 import java.util.*;
@@ -128,6 +129,12 @@ public class GlobalApp extends Application implements LibFreeRDP.EventListener {
         sessionMap.put(Integer.valueOf(session.getInstance()), session);
         return session;
     }
+    
+    static public SessionState createSession(Uri openUri, Context context) {
+        SessionState session = new SessionState(LibFreeRDP.newInstance(context), openUri);
+        sessionMap.put(Integer.valueOf(session.getInstance()), session);
+        return session;
+    }
 
     static public SessionState getSession(int instance) {
         return sessionMap.get(instance);
index e869d59..a66a6a0 100644 (file)
@@ -11,6 +11,7 @@ package com.freerdp.freerdpcore.application;
 
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -21,6 +22,7 @@ public class SessionState implements Parcelable
 {
        private int instance;
        private BookmarkBase bookmark;
+       private Uri openUri;
        private BitmapDrawable surface;
        private LibFreeRDP.UIEventListener uiEventListener;
        
@@ -28,6 +30,7 @@ public class SessionState implements Parcelable
        {
                instance = parcel.readInt();
                bookmark = parcel.readParcelable(null);
+               openUri = parcel.readParcelable(null);
 
                Bitmap bitmap = parcel.readParcelable(null);
                surface = new BitmapDrawable(bitmap);
@@ -37,11 +40,24 @@ public class SessionState implements Parcelable
        {
                this.instance = instance;
                this.bookmark = bookmark;
+               this.openUri = null;
+               this.uiEventListener = null;
+       }
+       
+       public SessionState(int instance, Uri openUri)
+       {
+               this.instance = instance;
+               this.bookmark = null;
+               this.openUri = openUri;
                this.uiEventListener = null;
        }
        
        public void connect() {
-               LibFreeRDP.setConnectionInfo(instance, bookmark);
+               if (bookmark != null) {
+                       LibFreeRDP.setConnectionInfo(instance, bookmark);
+               } else {
+                       LibFreeRDP.setConnectionInfo(instance, openUri);
+               }
                LibFreeRDP.connect(instance);
        }
        
@@ -53,6 +69,10 @@ public class SessionState implements Parcelable
                return bookmark;
        }
        
+       public Uri getOpenUri() {
+               return openUri;
+       }
+       
        public LibFreeRDP.UIEventListener getUIEventListener() {
                return uiEventListener;
        }
@@ -90,6 +110,7 @@ public class SessionState implements Parcelable
        public void writeToParcel(Parcel out, int flags) {              
                out.writeInt(instance);
                out.writeParcelable(bookmark, flags);
+               out.writeParcelable(openUri, flags);
                out.writeParcelable(surface.getBitmap(), flags);
        }
 }
index 453a0ba..8eaac0a 100644 (file)
@@ -29,6 +29,7 @@ import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.inputmethodservice.Keyboard;
 import android.inputmethodservice.KeyboardView;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -240,6 +241,11 @@ public class SessionActivity extends ActionBarActivity implements
                                progressDialog = null;
                        }
 
+                       if (session.getBookmark() == null) {
+                               // Return immediately if we launch from URI
+                               return;
+                       }
+
                        // add hostname to history if quick connect was used
                        Bundle bundle = getIntent().getExtras();
                        if (bundle != null
@@ -614,9 +620,14 @@ public class SessionActivity extends ActionBarActivity implements
        }
 
        private void processIntent(Intent intent) {
-               // get either session instance or create one from a bookmark
+               // get either session instance or create one from a bookmark/uri
                Bundle bundle = intent.getExtras();
-               if (bundle.containsKey(PARAM_INSTANCE)) {
+               Uri openUri = intent.getData();
+               if (openUri != null) {
+                       // Launched from URI, e.g:
+                       // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=-
+                       connect(openUri);
+               } else if (bundle.containsKey(PARAM_INSTANCE)) {
                        int inst = bundle.getInt(PARAM_INSTANCE);
                        session = GlobalApp.getSession(inst);
                        bitmap = session.getSurface().getBitmap();
@@ -648,7 +659,6 @@ public class SessionActivity extends ActionBarActivity implements
 
        private void connect(BookmarkBase bookmark) {
                session = GlobalApp.createSession(bookmark, getApplicationContext());
-               session.setUIEventListener(this);
 
                BookmarkBase.ScreenSettings screenSettings = session.getBookmark()
                                .getActiveScreenSettings();
@@ -673,8 +683,20 @@ public class SessionActivity extends ActionBarActivity implements
                        screenSettings.setWidth(screen_width);
                }
 
+               connectWithTitle(bookmark.getLabel());
+       }
+
+       private void connect(Uri openUri) {
+               session = GlobalApp.createSession(openUri, getApplicationContext());
+
+               connectWithTitle(openUri.getAuthority());
+       }
+
+       private void connectWithTitle(String title) {
+               session.setUIEventListener(this);
+
                progressDialog = new ProgressDialog(this);
-               progressDialog.setTitle(bookmark.getLabel());
+               progressDialog.setTitle(title);
                progressDialog.setMessage(getResources().getText(
                                R.string.dlg_msg_connecting));
                progressDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, "Cancel",
@@ -699,7 +721,7 @@ public class SessionActivity extends ActionBarActivity implements
        // binds the current session to the activity by wiring it up with the
        // sessionView and updating all internal objects accordingly
        private void bindSession() {
-               Log.v("SessionActivity", "bindSession called");
+               Log.v(TAG, "bindSession called");
                session.setUIEventListener(this);
                sessionView.onSurfaceChange(session);
                scrollView.requestLayout();
@@ -972,6 +994,11 @@ public class SessionActivity extends ActionBarActivity implements
 
                session.setSurface(new BitmapDrawable(bitmap));
 
+               if (session.getBookmark() == null) {
+                       // Return immediately if we launch from URI
+                       return;
+               }
+
                // check this settings and initial settings - if they are not equal the
                // server doesn't support our settings
                // FIXME: the additional check (settings.getWidth() != width + 1) is for
index d1d337a..c4bb57f 100644 (file)
@@ -17,6 +17,7 @@ import com.freerdp.freerdpcore.domain.ManualBookmark;
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -206,9 +207,7 @@ public class LibFreeRDP {
             args.add("/gfx");
         }
 
-        if (flags.getH264()) {
-            args.add("/h264");
-        }
+        args.add(addFlag("gfx-h264", flags.getH264()));
 
         args.add(addFlag("wallpaper", flags.getWallpaper()));
         args.add(addFlag("window-drag", flags.getFullWindowDrag()));
@@ -273,6 +272,57 @@ public class LibFreeRDP {
         String[] arrayArgs = args.toArray(new String[args.size()]);
         return freerdp_parse_arguments(inst, arrayArgs);
     }
+    
+    public static boolean setConnectionInfo(int inst, Uri openUri) {
+        ArrayList<String> args = new ArrayList<String>();
+
+        // Parse URI from query string. Same key overwrite previous one
+        // freerdp://user@ip:port/connect?sound=&rfx=&p=password&clipboard=%2b&themes=-
+     
+        // Now we only support Software GDI
+        args.add(TAG);
+        args.add("/gdi:sw");
+        
+        // Parse hostname and port. Set to 'v' argument
+        String hostname = openUri.getHost();
+        int port = openUri.getPort();
+        if (hostname != null) {
+            hostname = hostname + ((port == -1) ? "" : (":" + String.valueOf(port)));
+            args.add("/v:" + hostname);
+        }
+        
+        String user = openUri.getUserInfo();
+        if (user != null) {
+            args.add("/u:" + user);
+        }
+        
+        for (String key: openUri.getQueryParameterNames()) {
+            String value = openUri.getQueryParameter(key);
+            
+            if (value.isEmpty()) {
+                // Query: key=
+                // To freerdp argument: /key
+                args.add("/" + key);
+            } else if (value.equals("-") || value.equals("+")) {
+                // Query: key=- or key=+
+                // To freerdp argument: -key or +key
+                args.add(value+key);
+            } else {
+                // Query: key=value
+                // To freerdp argument: /key:value
+                if (key.equals("drive") && value.equals("sdcard")) { 
+                    // Special for sdcard redirect
+                    String path = android.os.Environment.getExternalStorageDirectory().getPath();
+                    value = "sdcard," + path;
+                }
+
+                args.add("/" + key + ":" + value);
+            }
+        }
+        
+        String[] arrayArgs = args.toArray(new String[args.size()]);
+        return freerdp_parse_arguments(inst, arrayArgs);
+    }
 
     public static boolean updateGraphics(int inst, Bitmap bitmap, int x, int y, int width, int height) {
         return freerdp_update_graphics(inst, bitmap, x, y, width, height);