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;
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.
*/
-public class Desktop extends Activity {
+public class Desktop extends Activity implements View.OnSystemUiVisibilityChangeListener {
+ /** Web page to be displayed in the Help screen when launched from this activity. */
+ private static final String HELP_URL =
+ "http://support.google.com/chrome/?p=mobile_crd_connecthost";
+
/** The surface that displays the remote host's desktop feed. */
private DesktopView mRemoteHostDesktop;
/** 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) {
// Ensure the button is initially hidden.
showActionBar();
+
+ View decorView = getWindow().getDecorView();
+ decorView.setOnSystemUiVisibilityChangeListener(this);
}
/** Called when the activity is finally finished. */
return super.onCreateOptionsMenu(menu);
}
+ /** Called whenever the visibility of the system status bar or navigation bar changes. */
+ @Override
+ public void onSystemUiVisibilityChange(int visibility) {
+ // Ensure the action-bar's visibility matches that of the system controls. This
+ // minimizes the number of states the UI can be in, to keep things simple for the user.
+
+ // Determine if the system is in fullscreen/lights-out mode. LOW_PROFILE is needed since
+ // it's the only flag supported in 4.0. But it is not sufficient in itself; when
+ // 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 = getSystemUiFlags();
+ if ((visibility & fullscreenFlags) != 0) {
+ hideActionBar();
+ } else {
+ showActionBar();
+ }
+ }
+
+ @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();
+
+ View decorView = getWindow().getDecorView();
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
public void hideActionBar() {
mOverlayButton.setVisibility(View.VISIBLE);
getActionBar().hide();
+
+ View decorView = getWindow().getDecorView();
+
+ // 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 = 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.
+ // IMMERSIVE(_STICKY) fixes this problem and allows the user to interact with the app while
+ // keeping the navigation controls hidden. This flag was introduced in 4.4, later than
+ // HIDE_NAVIGATION, and so a runtime check is needed before setting either of these flags.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ flags |= (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+
+ decorView.setSystemUiVisibility(flags);
}
/** The overlay button's onClick handler. */
/** Called whenever an action bar button is pressed. */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.actionbar_keyboard:
- ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0);
- return true;
-
- case R.id.actionbar_hide:
- hideActionBar();
- return true;
-
- case R.id.actionbar_disconnect:
- JniInterface.disconnectFromHost();
- return true;
-
- case R.id.actionbar_send_ctrl_alt_del:
- {
- int[] keys = {
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_FORWARD_DEL,
- };
- for (int key : keys) {
- JniInterface.keyboardAction(key, true);
- }
- for (int key : keys) {
- JniInterface.keyboardAction(key, false);
- }
- }
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
+ int id = item.getItemId();
+ if (id == R.id.actionbar_keyboard) {
+ ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0);
+ return true;
+ }
+ if (id == R.id.actionbar_hide) {
+ hideActionBar();
+ return true;
+ }
+ if (id == R.id.actionbar_disconnect) {
+ JniInterface.disconnectFromHost();
+ return true;
+ }
+ if (id == R.id.actionbar_send_ctrl_alt_del) {
+ int[] keys = {
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_FORWARD_DEL,
+ };
+ for (int key : keys) {
+ JniInterface.sendKeyEvent(key, true);
+ }
+ for (int key : keys) {
+ JniInterface.sendKeyEvent(key, false);
+ }
+ return true;
}
+ if (id == R.id.actionbar_help) {
+ HelpActivity.launch(this, HELP_URL);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
}
/**
*/
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
- boolean depressed = event.getAction() == KeyEvent.ACTION_DOWN;
+ 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 true;
+ }
+
+ boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
- switch (event.getKeyCode()) {
+ // 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;
+ }
+
+ if (!pressed && mPressedTextKeys.contains(keyCode)) {
+ mPressedTextKeys.remove(keyCode);
+ return true;
+ }
+
+ switch (keyCode) {
case KeyEvent.KEYCODE_AT:
- JniInterface.keyboardAction(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
- JniInterface.keyboardAction(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.keyboardAction(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
- JniInterface.keyboardAction(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.keyboardAction(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
- JniInterface.keyboardAction(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.keyboardAction(KeyEvent.KEYCODE_SHIFT_LEFT, depressed);
- JniInterface.keyboardAction(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.keyboardAction(event.getKeyCode(), depressed);
+ return JniInterface.sendKeyEvent(keyCode, pressed);
}
-
- return super.dispatchKeyEvent(event);
}
}