Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / remoting / android / java / src / org / chromium / chromoting / Desktop.java
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chromoting;
6
7 import android.annotation.SuppressLint;
8 import android.content.res.Configuration;
9 import android.os.Build;
10 import android.os.Bundle;
11 import android.support.v7.app.ActionBarActivity;
12 import android.view.KeyCharacterMap;
13 import android.view.KeyEvent;
14 import android.view.Menu;
15 import android.view.MenuItem;
16 import android.view.View;
17 import android.view.inputmethod.InputMethodManager;
18 import android.widget.ImageButton;
19
20 import org.chromium.chromoting.jni.JniInterface;
21
22 import java.util.Set;
23 import java.util.TreeSet;
24
25 /**
26  * A simple screen that does nothing except display a DesktopView and notify it of rotations.
27  */
28 public class Desktop extends ActionBarActivity implements View.OnSystemUiVisibilityChangeListener {
29     /** Web page to be displayed in the Help screen when launched from this activity. */
30     private static final String HELP_URL =
31             "http://support.google.com/chrome/?p=mobile_crd_connecthost";
32
33     /** The surface that displays the remote host's desktop feed. */
34     private DesktopView mRemoteHostDesktop;
35
36     /** The button used to show the action bar. */
37     private ImageButton mOverlayButton;
38
39     /** Set of pressed keys for which we've sent TextEvent. */
40     private Set<Integer> mPressedTextKeys = new TreeSet<Integer>();
41
42     private ActivityLifecycleListener mActivityLifecycleListener;
43
44
45     /** Called when the activity is first created. */
46     @Override
47     public void onCreate(Bundle savedInstanceState) {
48         super.onCreate(savedInstanceState);
49         setContentView(R.layout.desktop);
50         mRemoteHostDesktop = (DesktopView)findViewById(R.id.desktop_view);
51         mOverlayButton = (ImageButton)findViewById(R.id.desktop_overlay_button);
52         mRemoteHostDesktop.setDesktop(this);
53
54         // Ensure the button is initially hidden.
55         showActionBar();
56
57         View decorView = getWindow().getDecorView();
58         decorView.setOnSystemUiVisibilityChangeListener(this);
59
60         mActivityLifecycleListener = CapabilityManager.getInstance()
61             .onActivityAcceptingListener(this, Capabilities.CAST_CAPABILITY);
62         mActivityLifecycleListener.onActivityCreated(this, savedInstanceState);
63     }
64
65     @Override
66     protected void onStart() {
67         super.onStart();
68         mActivityLifecycleListener.onActivityStarted(this);
69     }
70
71     @Override
72     protected void onPause() {
73       if (isFinishing()) {
74         mActivityLifecycleListener.onActivityPaused(this);
75       }
76       super.onPause();
77     }
78
79     @Override
80     public void onResume() {
81         super.onResume();
82         mActivityLifecycleListener.onActivityResumed(this);
83     }
84
85     @Override
86     protected void onStop() {
87         mActivityLifecycleListener.onActivityStopped(this);
88         super.onStop();
89     }
90
91     /** Called when the activity is finally finished. */
92     @Override
93     public void onDestroy() {
94         super.onDestroy();
95         JniInterface.disconnectFromHost();
96     }
97
98     /** Called when the display is rotated (as registered in the manifest). */
99     @Override
100     public void onConfigurationChanged(Configuration newConfig) {
101         super.onConfigurationChanged(newConfig);
102         mRemoteHostDesktop.onScreenConfigurationChanged();
103     }
104
105     /** Called to initialize the action bar. */
106     @Override
107     public boolean onCreateOptionsMenu(Menu menu) {
108         getMenuInflater().inflate(R.menu.desktop_actionbar, menu);
109
110         mActivityLifecycleListener.onActivityCreatedOptionsMenu(this, menu);
111
112         return super.onCreateOptionsMenu(menu);
113     }
114
115     /** Called whenever the visibility of the system status bar or navigation bar changes. */
116     @Override
117     public void onSystemUiVisibilityChange(int visibility) {
118         // Ensure the action-bar's visibility matches that of the system controls. This
119         // minimizes the number of states the UI can be in, to keep things simple for the user.
120
121         // Determine if the system is in fullscreen/lights-out mode. LOW_PROFILE is needed since
122         // it's the only flag supported in 4.0. But it is not sufficient in itself; when
123         // IMMERSIVE_STICKY mode is used, the system clears this flag (leaving the FULLSCREEN flag
124         // set) when the user swipes the edge to reveal the bars temporarily. When this happens,
125         // the action-bar should remain hidden.
126         int fullscreenFlags = getSystemUiFlags();
127         if ((visibility & fullscreenFlags) != 0) {
128             hideActionBar();
129         } else {
130             showActionBar();
131         }
132     }
133
134     @SuppressLint("InlinedApi")
135     private int getSystemUiFlags() {
136         int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
137         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
138             flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
139         }
140         return flags;
141     }
142
143     public void showActionBar() {
144         mOverlayButton.setVisibility(View.INVISIBLE);
145         getActionBar().show();
146
147         View decorView = getWindow().getDecorView();
148         decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
149     }
150
151     @SuppressLint("InlinedApi")
152     public void hideActionBar() {
153         mOverlayButton.setVisibility(View.VISIBLE);
154         getActionBar().hide();
155
156         View decorView = getWindow().getDecorView();
157
158         // LOW_PROFILE gives the status and navigation bars a "lights-out" appearance.
159         // FULLSCREEN hides the status bar on supported devices (4.1 and above).
160         int flags = getSystemUiFlags();
161
162         // HIDE_NAVIGATION hides the navigation bar. However, if the user touches the screen, the
163         // event is not seen by the application and instead the navigation bar is re-shown.
164         // IMMERSIVE(_STICKY) fixes this problem and allows the user to interact with the app while
165         // keeping the navigation controls hidden. This flag was introduced in 4.4, later than
166         // HIDE_NAVIGATION, and so a runtime check is needed before setting either of these flags.
167         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
168             flags |= (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
169         }
170
171         decorView.setSystemUiVisibility(flags);
172     }
173
174     /** The overlay button's onClick handler. */
175     public void onOverlayButtonPressed(View view) {
176         showActionBar();
177     }
178
179     /** Called whenever an action bar button is pressed. */
180     @Override
181     public boolean onOptionsItemSelected(MenuItem item) {
182         int id = item.getItemId();
183
184         mActivityLifecycleListener.onActivityOptionsItemSelected(this, item);
185
186         if (id == R.id.actionbar_keyboard) {
187             ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0);
188             return true;
189         }
190         if (id == R.id.actionbar_hide) {
191             hideActionBar();
192             return true;
193         }
194         if (id == R.id.actionbar_disconnect) {
195             JniInterface.disconnectFromHost();
196             return true;
197         }
198         if (id == R.id.actionbar_send_ctrl_alt_del) {
199             int[] keys = {
200                 KeyEvent.KEYCODE_CTRL_LEFT,
201                 KeyEvent.KEYCODE_ALT_LEFT,
202                 KeyEvent.KEYCODE_FORWARD_DEL,
203             };
204             for (int key : keys) {
205                 JniInterface.sendKeyEvent(key, true);
206             }
207             for (int key : keys) {
208                 JniInterface.sendKeyEvent(key, false);
209             }
210             return true;
211         }
212         if (id == R.id.actionbar_help) {
213             HelpActivity.launch(this, HELP_URL);
214             return true;
215         }
216         return super.onOptionsItemSelected(item);
217     }
218
219     /**
220      * Called once when a keyboard key is pressed, then again when that same key is released. This
221      * is not guaranteed to be notified of all soft keyboard events: certian keyboards might not
222      * call it at all, while others might skip it in certain situations (e.g. swipe input).
223      */
224     @Override
225     public boolean dispatchKeyEvent(KeyEvent event) {
226         int keyCode = event.getKeyCode();
227
228         // Dispatch the back button to the system to handle navigation
229         if (keyCode == KeyEvent.KEYCODE_BACK) {
230             return super.dispatchKeyEvent(event);
231         }
232
233         // Send TextEvent in two cases:
234         //   1. This is an ACTION_MULTIPLE event.
235         //   2. The event was generated by on-screen keyboard and Ctrl, Alt and
236         //      Meta are not pressed.
237         // This ensures that on-screen keyboard always injects input that
238         // correspond to what user sees on the screen, while physical keyboard
239         // acts as if it is connected to the remote host.
240         if (event.getAction() == KeyEvent.ACTION_MULTIPLE) {
241             JniInterface.sendTextEvent(event.getCharacters());
242             return true;
243         }
244
245         boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
246
247         // For Enter getUnicodeChar() returns 10 (line feed), but we still
248         // want to send it as KeyEvent.
249         int unicode = keyCode != KeyEvent.KEYCODE_ENTER ? event.getUnicodeChar() : 0;
250
251         boolean no_modifiers = !event.isAltPressed() &&
252                                !event.isCtrlPressed() && !event.isMetaPressed();
253
254         if (event.getDeviceId() == KeyCharacterMap.VIRTUAL_KEYBOARD &&
255             pressed && unicode != 0 && no_modifiers) {
256             mPressedTextKeys.add(keyCode);
257             int[] codePoints = { unicode };
258             JniInterface.sendTextEvent(new String(codePoints, 0, 1));
259             return true;
260         }
261
262         if (!pressed && mPressedTextKeys.contains(keyCode)) {
263             mPressedTextKeys.remove(keyCode);
264             return true;
265         }
266
267         switch (keyCode) {
268             case KeyEvent.KEYCODE_AT:
269                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
270                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_2, pressed);
271                 return true;
272
273             case KeyEvent.KEYCODE_POUND:
274                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
275                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_3, pressed);
276                 return true;
277
278             case KeyEvent.KEYCODE_STAR:
279                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
280                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_8, pressed);
281                 return true;
282
283             case KeyEvent.KEYCODE_PLUS:
284                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
285                 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_EQUALS, pressed);
286                 return true;
287
288             default:
289                 // We try to send all other key codes to the host directly.
290                 return JniInterface.sendKeyEvent(keyCode, pressed);
291         }
292     }
293 }