Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / remoting / android / java / src / org / chromium / chromoting / Desktop.java
index e4a8d0c..d301c09 100644 (file)
@@ -4,10 +4,12 @@
 
 package org.chromium.chromoting;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -17,6 +19,9 @@ import android.widget.ImageButton;
 
 import org.chromium.chromoting.jni.JniInterface;
 
+import java.util.Set;
+import java.util.TreeSet;
+
 /**
  * A simple screen that does nothing except display a DesktopView and notify it of rotations.
  */
@@ -31,6 +36,9 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
     /** The button used to show the action bar. */
     private ImageButton mOverlayButton;
 
+    /** Set of pressed keys for which we've sent TextEvent. */
+    private Set<Integer> mPressedTextKeys = new TreeSet<Integer>();
+
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -79,10 +87,7 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
         // IMMERSIVE_STICKY mode is used, the system clears this flag (leaving the FULLSCREEN flag
         // set) when the user swipes the edge to reveal the bars temporarily. When this happens,
         // the action-bar should remain hidden.
-        int fullscreenFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            fullscreenFlags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
-        }
+        int fullscreenFlags = getSystemUiFlags();
         if ((visibility & fullscreenFlags) != 0) {
             hideActionBar();
         } else {
@@ -90,6 +95,15 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
         }
     }
 
+    @SuppressLint("InlinedApi")
+    private int getSystemUiFlags() {
+        int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+        }
+        return flags;
+    }
+
     public void showActionBar() {
         mOverlayButton.setVisibility(View.INVISIBLE);
         getActionBar().show();
@@ -106,10 +120,7 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
 
         // LOW_PROFILE gives the status and navigation bars a "lights-out" appearance.
         // FULLSCREEN hides the status bar on supported devices (4.1 and above).
-        int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
-        }
+        int flags = getSystemUiFlags();
 
         // HIDE_NAVIGATION hides the navigation bar. However, if the user touches the screen, the
         // event is not seen by the application and instead the navigation bar is re-shown.
@@ -172,50 +183,71 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
      */
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        // Send ACTION_MULTIPLE event as TextEvent.
-        //
-        // TODO(sergeyu): For all keys on English keyboard Android generates
-        // ACTION_DOWN/ACTION_UP events, so they are sent as KeyEvent instead of
-        // TextEvent. As result the host may handle them as non-English chars
-        // when it has non-English layout selected, which might be confusing for
-        // the user. This code should be fixed to send all text input events as
-        // TextEvent, but it cannot be done now because not all hosts support
-        // TextEvent. Also, to handle keyboard shortcuts properly this code will
-        // need to track the state of modifier keys (such as Ctrl or Alt) and
-        // send KeyEvents in the case any of the modifier keys are pressed.
+        int keyCode = event.getKeyCode();
+
+        // Dispatch the back button to the system to handle navigation
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return super.dispatchKeyEvent(event);
+        }
+
+        // Send TextEvent in two cases:
+        //   1. This is an ACTION_MULTIPLE event.
+        //   2. The event was generated by on-screen keyboard and Ctrl, Alt and
+        //      Meta are not pressed.
+        // This ensures that on-screen keyboard always injects input that
+        // correspond to what user sees on the screen, while physical keyboard
+        // acts as if it is connected to the remote host.
         if (event.getAction() == KeyEvent.ACTION_MULTIPLE) {
             JniInterface.sendTextEvent(event.getCharacters());
-            return super.dispatchKeyEvent(event);
+            return true;
+        }
+
+        boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
+
+        // For Enter getUnicodeChar() returns 10 (line feed), but we still
+        // want to send it as KeyEvent.
+        int unicode = keyCode != KeyEvent.KEYCODE_ENTER ? event.getUnicodeChar() : 0;
+
+        boolean no_modifiers = !event.isAltPressed() &&
+                               !event.isCtrlPressed() && !event.isMetaPressed();
+
+        if (event.getDeviceId() == KeyCharacterMap.VIRTUAL_KEYBOARD &&
+            pressed && unicode != 0 && no_modifiers) {
+            mPressedTextKeys.add(keyCode);
+            int[] codePoints = { unicode };
+            JniInterface.sendTextEvent(new String(codePoints, 0, 1));
+            return true;
         }
 
-        boolean depressed = event.getAction() == KeyEvent.ACTION_DOWN;
+        if (!pressed && mPressedTextKeys.contains(keyCode)) {
+            mPressedTextKeys.remove(keyCode);
+            return true;
+        }
 
-        switch (event.getKeyCode()) {
+        switch (keyCode) {
             case KeyEvent.KEYCODE_AT:
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_2, depressed);
-                break;
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_2, pressed);
+                return true;
 
             case KeyEvent.KEYCODE_POUND:
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_3, depressed);
-                break;
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_3, pressed);
+                return true;
 
             case KeyEvent.KEYCODE_STAR:
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_8, depressed);
-                break;
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_8, pressed);
+                return true;
 
             case KeyEvent.KEYCODE_PLUS:
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
-                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_EQUALS, depressed);
-                break;
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
+                JniInterface.sendKeyEvent(KeyEvent.KEYCODE_EQUALS, pressed);
+                return true;
 
             default:
                 // We try to send all other key codes to the host directly.
-                JniInterface.sendKeyEvent(event.getKeyCode(), depressed);
+                return JniInterface.sendKeyEvent(keyCode, pressed);
         }
-
-        return super.dispatchKeyEvent(event);
     }
 }