Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / remoting / android / java / src / org / chromium / chromoting / Desktop.java
blobc4f36957e6550dc97032d13348341d9b348e1166
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;
21 import java.util.Set;
22 import java.util.TreeSet;
24 /**
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. */
41 @Override
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);
60 @Override
61 protected void onStart() {
62 super.onStart();
63 mActivityLifecycleListener.onActivityStarted(this);
64 JniInterface.enableVideoChannel(true);
67 @Override
68 protected void onPause() {
69 if (isFinishing()) mActivityLifecycleListener.onActivityPaused(this);
70 super.onPause();
71 JniInterface.enableVideoChannel(false);
74 @Override
75 public void onResume() {
76 super.onResume();
77 mActivityLifecycleListener.onActivityResumed(this);
78 JniInterface.enableVideoChannel(true);
81 @Override
82 protected void onStop() {
83 mActivityLifecycleListener.onActivityStopped(this);
84 super.onStop();
85 JniInterface.enableVideoChannel(false);
88 /** Called when the activity is finally finished. */
89 @Override
90 public void onDestroy() {
91 super.onDestroy();
92 JniInterface.disconnectFromHost();
95 /** Called when the display is rotated (as registered in the manifest). */
96 @Override
97 public void onConfigurationChanged(Configuration newConfig) {
98 super.onConfigurationChanged(newConfig);
99 mRemoteHostDesktop.onScreenConfigurationChanged();
102 /** Called to initialize the action bar. */
103 @Override
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. */
113 @Override
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();
126 } else {
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;
137 return flags;
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
160 // fullscreen state.
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. */
185 @Override
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);
193 return true;
195 if (id == R.id.actionbar_hide) {
196 hideActionBar();
197 return true;
199 if (id == R.id.actionbar_disconnect || id == android.R.id.home) {
200 JniInterface.disconnectFromHost();
201 return true;
203 if (id == R.id.actionbar_send_ctrl_alt_del) {
204 int[] keys = {
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);
215 return true;
217 if (id == R.id.actionbar_help) {
218 HelpActivity.launch(this, HELP_URL);
219 return true;
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).
229 @Override
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());
247 return true;
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));
264 return true;
267 if (!pressed && mPressedTextKeys.contains(keyCode)) {
268 mPressedTextKeys.remove(keyCode);
269 return true;
272 switch (keyCode) {
273 case KeyEvent.KEYCODE_AT:
274 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
275 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_2, pressed);
276 return true;
278 case KeyEvent.KEYCODE_POUND:
279 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
280 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_3, pressed);
281 return true;
283 case KeyEvent.KEYCODE_STAR:
284 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
285 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_8, pressed);
286 return true;
288 case KeyEvent.KEYCODE_PLUS:
289 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_SHIFT_LEFT, pressed);
290 JniInterface.sendKeyEvent(KeyEvent.KEYCODE_EQUALS, pressed);
291 return true;
293 default:
294 // We try to send all other key codes to the host directly.
295 return JniInterface.sendKeyEvent(keyCode, pressed);