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.
5 package org
.chromium
.chromoting
;
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
;
19 import org
.chromium
.chromoting
.jni
.JniInterface
;
22 import java
.util
.TreeSet
;
25 * A simple screen that does nothing except display a DesktopView and notify it of rotations.
27 public class Desktop
extends ActionBarActivity
implements View
.OnSystemUiVisibilityChangeListener
{
28 /** Web page to be displayed in the Help screen when launched from this activity. */
29 private static final String HELP_URL
=
30 "https://support.google.com/chrome/?p=mobile_crd_connecthost";
32 /** The surface that displays the remote host's desktop feed. */
33 private DesktopView mRemoteHostDesktop
;
35 /** Set of pressed keys for which we've sent TextEvent. */
36 private Set
<Integer
> mPressedTextKeys
= new TreeSet
<Integer
>();
38 private ActivityLifecycleListener mActivityLifecycleListener
;
40 /** Called when the activity is first created. */
42 public void onCreate(Bundle savedInstanceState
) {
43 super.onCreate(savedInstanceState
);
44 setContentView(R
.layout
.desktop
);
45 mRemoteHostDesktop
= (DesktopView
) findViewById(R
.id
.desktop_view
);
46 mRemoteHostDesktop
.setDesktop(this);
48 // For this Activity, the home button in the action bar acts as a Disconnect button, so
49 // set the description for accessibility/screen readers.
50 getSupportActionBar().setHomeActionContentDescription(R
.string
.disconnect_myself_button
);
52 View decorView
= getWindow().getDecorView();
53 decorView
.setOnSystemUiVisibilityChangeListener(this);
55 mActivityLifecycleListener
= CapabilityManager
.getInstance()
56 .onActivityAcceptingListener(this, Capabilities
.CAST_CAPABILITY
);
57 mActivityLifecycleListener
.onActivityCreated(this, savedInstanceState
);
61 protected void onStart() {
63 mActivityLifecycleListener
.onActivityStarted(this);
64 JniInterface
.enableVideoChannel(true);
68 protected void onPause() {
69 if (isFinishing()) mActivityLifecycleListener
.onActivityPaused(this);
71 JniInterface
.enableVideoChannel(false);
75 public void onResume() {
77 mActivityLifecycleListener
.onActivityResumed(this);
78 JniInterface
.enableVideoChannel(true);
82 protected void onStop() {
83 mActivityLifecycleListener
.onActivityStopped(this);
85 JniInterface
.enableVideoChannel(false);
88 /** Called when the activity is finally finished. */
90 public void onDestroy() {
92 JniInterface
.disconnectFromHost();
95 /** Called when the display is rotated (as registered in the manifest). */
97 public void onConfigurationChanged(Configuration newConfig
) {
98 super.onConfigurationChanged(newConfig
);
99 mRemoteHostDesktop
.onScreenConfigurationChanged();
102 /** Called to initialize the action bar. */
104 public boolean onCreateOptionsMenu(Menu menu
) {
105 getMenuInflater().inflate(R
.menu
.desktop_actionbar
, menu
);
107 mActivityLifecycleListener
.onActivityCreatedOptionsMenu(this, menu
);
109 return super.onCreateOptionsMenu(menu
);
112 /** Called whenever the visibility of the system status bar or navigation bar changes. */
114 public void onSystemUiVisibilityChange(int visibility
) {
115 // Ensure the action-bar's visibility matches that of the system controls. This
116 // minimizes the number of states the UI can be in, to keep things simple for the user.
118 // Determine if the system is in fullscreen/lights-out mode. LOW_PROFILE is needed since
119 // it's the only flag supported in 4.0. But it is not sufficient in itself; when
120 // IMMERSIVE_STICKY mode is used, the system clears this flag (leaving the FULLSCREEN flag
121 // set) when the user swipes the edge to reveal the bars temporarily. When this happens,
122 // the action-bar should remain hidden.
123 int fullscreenFlags
= getSystemUiFlags();
124 if ((visibility
& fullscreenFlags
) != 0) {
125 hideActionBarWithoutSystemUi();
127 showActionBarWithoutSystemUi();
131 @SuppressLint("InlinedApi")
132 private int getSystemUiFlags() {
133 int flags
= View
.SYSTEM_UI_FLAG_LOW_PROFILE
;
134 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.JELLY_BEAN
) {
135 flags
|= View
.SYSTEM_UI_FLAG_FULLSCREEN
;
140 public void showActionBar() {
141 // Request exit from any fullscreen mode. The action-bar controls will be shown in response
142 // to the SystemUiVisibility notification. The visibility of the action-bar should be tied
143 // to the fullscreen state of the system, so there's no need to explicitly show it here.
144 View decorView
= getWindow().getDecorView();
145 decorView
.setSystemUiVisibility(View
.SYSTEM_UI_FLAG_VISIBLE
);
148 /** Shows the action bar without changing SystemUiVisibility. */
149 private void showActionBarWithoutSystemUi() {
150 getSupportActionBar().show();
153 @SuppressLint("InlinedApi")
154 public void hideActionBar() {
155 // Request the device to enter fullscreen mode. Don't hide the controls yet, because the
156 // system might not honor the fullscreen request immediately (for example, if the
157 // keyboard is visible, the system might delay fullscreen until the keyboard is hidden).
158 // The controls will be hidden in response to the SystemUiVisibility notification.
159 // This helps ensure that the visibility of the controls is synchronized with the
161 View decorView
= getWindow().getDecorView();
163 // LOW_PROFILE gives the status and navigation bars a "lights-out" appearance.
164 // FULLSCREEN hides the status bar on supported devices (4.1 and above).
165 int flags
= getSystemUiFlags();
167 // HIDE_NAVIGATION hides the navigation bar. However, if the user touches the screen, the
168 // event is not seen by the application and instead the navigation bar is re-shown.
169 // IMMERSIVE(_STICKY) fixes this problem and allows the user to interact with the app while
170 // keeping the navigation controls hidden. This flag was introduced in 4.4, later than
171 // HIDE_NAVIGATION, and so a runtime check is needed before setting either of these flags.
172 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.KITKAT
) {
173 flags
|= (View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View
.SYSTEM_UI_FLAG_IMMERSIVE
);
176 decorView
.setSystemUiVisibility(flags
);
179 /** Hides the action bar without changing SystemUiVisibility. */
180 private void hideActionBarWithoutSystemUi() {
181 getSupportActionBar().hide();
184 /** Called whenever an action bar button is pressed. */
186 public boolean onOptionsItemSelected(MenuItem item
) {
187 int id
= item
.getItemId();
189 mActivityLifecycleListener
.onActivityOptionsItemSelected(this, item
);
191 if (id
== R
.id
.actionbar_keyboard
) {
192 ((InputMethodManager
) getSystemService(INPUT_METHOD_SERVICE
)).toggleSoftInput(0, 0);
195 if (id
== R
.id
.actionbar_hide
) {
199 if (id
== R
.id
.actionbar_disconnect
|| id
== android
.R
.id
.home
) {
200 JniInterface
.disconnectFromHost();
203 if (id
== R
.id
.actionbar_send_ctrl_alt_del
) {
205 KeyEvent
.KEYCODE_CTRL_LEFT
,
206 KeyEvent
.KEYCODE_ALT_LEFT
,
207 KeyEvent
.KEYCODE_FORWARD_DEL
,
209 for (int key
: keys
) {
210 JniInterface
.sendKeyEvent(key
, true);
212 for (int key
: keys
) {
213 JniInterface
.sendKeyEvent(key
, false);
217 if (id
== R
.id
.actionbar_help
) {
218 HelpActivity
.launch(this, HELP_URL
);
221 return super.onOptionsItemSelected(item
);
225 * Called once when a keyboard key is pressed, then again when that same key is released. This
226 * is not guaranteed to be notified of all soft keyboard events: certian keyboards might not
227 * call it at all, while others might skip it in certain situations (e.g. swipe input).
230 public boolean dispatchKeyEvent(KeyEvent event
) {
231 int keyCode
= event
.getKeyCode();
233 // Dispatch the back button to the system to handle navigation
234 if (keyCode
== KeyEvent
.KEYCODE_BACK
) {
235 return super.dispatchKeyEvent(event
);
238 // Send TextEvent in two cases:
239 // 1. This is an ACTION_MULTIPLE event.
240 // 2. The event was generated by on-screen keyboard and Ctrl, Alt and
241 // Meta are not pressed.
242 // This ensures that on-screen keyboard always injects input that
243 // correspond to what user sees on the screen, while physical keyboard
244 // acts as if it is connected to the remote host.
245 if (event
.getAction() == KeyEvent
.ACTION_MULTIPLE
) {
246 JniInterface
.sendTextEvent(event
.getCharacters());
250 boolean pressed
= event
.getAction() == KeyEvent
.ACTION_DOWN
;
252 // For Enter getUnicodeChar() returns 10 (line feed), but we still
253 // want to send it as KeyEvent.
254 int unicode
= keyCode
!= KeyEvent
.KEYCODE_ENTER ? event
.getUnicodeChar() : 0;
256 boolean no_modifiers
= !event
.isAltPressed()
257 && !event
.isCtrlPressed() && !event
.isMetaPressed();
259 if (event
.getDeviceId() == KeyCharacterMap
.VIRTUAL_KEYBOARD
260 && pressed
&& unicode
!= 0 && no_modifiers
) {
261 mPressedTextKeys
.add(keyCode
);
262 int[] codePoints
= { unicode
};
263 JniInterface
.sendTextEvent(new String(codePoints
, 0, 1));
267 if (!pressed
&& mPressedTextKeys
.contains(keyCode
)) {
268 mPressedTextKeys
.remove(keyCode
);
273 case KeyEvent
.KEYCODE_AT
:
274 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_SHIFT_LEFT
, pressed
);
275 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_2
, pressed
);
278 case KeyEvent
.KEYCODE_POUND
:
279 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_SHIFT_LEFT
, pressed
);
280 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_3
, pressed
);
283 case KeyEvent
.KEYCODE_STAR
:
284 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_SHIFT_LEFT
, pressed
);
285 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_8
, pressed
);
288 case KeyEvent
.KEYCODE_PLUS
:
289 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_SHIFT_LEFT
, pressed
);
290 JniInterface
.sendKeyEvent(KeyEvent
.KEYCODE_EQUALS
, pressed
);
294 // We try to send all other key codes to the host directly.
295 return JniInterface
.sendKeyEvent(keyCode
, pressed
);