| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chromoting; |
| |
| import android.annotation.SuppressLint; |
| import android.content.res.Configuration; |
| import android.graphics.Color; |
| import android.graphics.drawable.ColorDrawable; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.support.v7.app.ActionBar.OnMenuVisibilityListener; |
| import android.support.v7.app.AppCompatActivity; |
| import android.support.v7.widget.Toolbar; |
| import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.View.OnLayoutChangeListener; |
| import android.view.View.OnTouchListener; |
| import android.view.animation.Animation; |
| import android.view.animation.AnimationUtils; |
| import android.view.inputmethod.InputMethodManager; |
| |
| import org.chromium.chromoting.help.HelpContext; |
| import org.chromium.chromoting.help.HelpSingleton; |
| import org.chromium.chromoting.jni.Client; |
| import org.chromium.ui.UiUtils; |
| |
| import java.util.List; |
| |
| /** |
| * A simple screen that does nothing except display a DesktopView and notify it of rotations. |
| */ |
| public class Desktop |
| extends AppCompatActivity implements View.OnSystemUiVisibilityChangeListener, |
| CapabilityManager.CapabilitiesChangedListener { |
| /** Used to set/store the selected input mode. */ |
| public enum InputMode { |
| UNKNOWN, |
| TRACKPAD, |
| TOUCH; |
| |
| public boolean isSet() { |
| return this != UNKNOWN; |
| } |
| } |
| |
| /** Preference used to track the last input mode selected by the user. */ |
| private static final String PREFERENCE_INPUT_MODE = "input_mode"; |
| |
| /** The amount of time to wait to hide the ActionBar after user input is seen. */ |
| private static final int ACTIONBAR_AUTO_HIDE_DELAY_MS = 3000; |
| |
| /** Duration for fade-in and fade-out animations for the ActionBar. */ |
| private static final int ACTIONBAR_ANIMATION_DURATION_MS = 250; |
| |
| private final Event.Raisable<SystemUiVisibilityChangedEventParameter> |
| mOnSystemUiVisibilityChanged = new Event.Raisable<>(); |
| |
| private final Event.Raisable<InputModeChangedEventParameter> mOnInputModeChanged = |
| new Event.Raisable<>(); |
| |
| private Client mClient; |
| private InputEventSender mInjector; |
| |
| private ActivityLifecycleListener mActivityLifecycleListener; |
| |
| /** Indicates whether a Soft Input UI (such as a keyboard) is visible. */ |
| private boolean mSoftInputVisible = false; |
| |
| /** Holds the scheduled task object which will be called to hide the ActionBar. */ |
| private Runnable mActionBarAutoHideTask; |
| |
| /** The Toolbar instance backing our SupportActionBar. */ |
| private Toolbar mToolbar; |
| |
| /** Tracks the current input mode (e.g. trackpad/touch). */ |
| private InputMode mInputMode = InputMode.UNKNOWN; |
| |
| /** Indicates whether the remote host supports touch injection. */ |
| private CapabilityManager.HostCapability mHostTouchCapability = |
| CapabilityManager.HostCapability.UNKNOWN; |
| |
| private DesktopView mRemoteHostDesktop; |
| |
| /** |
| * Indicates whether the device is connected to a non-hidden physical qwerty keyboard. This is |
| * set by {@link Desktop#setKeyboardState(Configuration)}. DO NOT request a soft keyboard when a |
| * physical keyboard exists, otherwise the activity will enter an undefined state where the soft |
| * keyboard never shows up meanwhile request to hide status bar always fails. |
| */ |
| private boolean mHasPhysicalKeyboard; |
| |
| /** Tracks whether the activity is in windowed mode. */ |
| private boolean mIsInWindowedMode = false; |
| |
| /** Called when the activity is first created. */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.desktop); |
| |
| mClient = Client.getInstance(); |
| mInjector = new InputEventSender(mClient); |
| |
| Preconditions.notNull(mClient); |
| |
| mToolbar = (Toolbar) findViewById(R.id.toolbar); |
| setSupportActionBar(mToolbar); |
| |
| mRemoteHostDesktop = (DesktopView) findViewById(R.id.desktop_view); |
| mRemoteHostDesktop.init(mClient, this, mClient.getRenderStub()); |
| |
| getSupportActionBar().setDisplayShowTitleEnabled(false); |
| getSupportActionBar().setDisplayHomeAsUpEnabled(true); |
| |
| // For this Activity, the home button in the action bar acts as a Disconnect button, so |
| // set the description for accessibility/screen readers. |
| getSupportActionBar().setHomeActionContentDescription(R.string.disconnect_myself_button); |
| |
| // The action bar is already shown when the activity is started however calling the |
| // function below will set our preferred system UI flags which will adjust the layout |
| // size of the canvas and we can avoid an initial resize event. |
| showSystemUi(); |
| |
| View decorView = getWindow().getDecorView(); |
| decorView.setOnSystemUiVisibilityChangeListener(this); |
| |
| // The background color is displayed when the user resizes the window in split-screen past |
| // the boundaries of the image we render. The default background is white and we use black |
| // for our canvas, thus there is a visual artifact when we draw the canvas over the |
| // background. Setting the background color to match our canvas will prevent the flash. |
| getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); |
| |
| mActivityLifecycleListener = mClient.getCapabilityManager().onActivityAcceptingListener( |
| this, Capabilities.CAST_CAPABILITY); |
| mActivityLifecycleListener.onActivityCreated(this, savedInstanceState); |
| |
| mInputMode = getInitialInputModeValue(); |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| attachSystemUiResizeListener(); |
| |
| // Suspend the ActionBar timer when the user interacts with the options menu. |
| getSupportActionBar().addOnMenuVisibilityListener(new OnMenuVisibilityListener() { |
| public void onMenuVisibilityChanged(boolean isVisible) { |
| if (isVisible) { |
| stopActionBarAutoHideTimer(); |
| } else { |
| startActionBarAutoHideTimer(); |
| } |
| } |
| }); |
| } else { |
| mRemoteHostDesktop.setFitsSystemWindows(true); |
| } |
| } |
| |
| @Override |
| protected void onStart() { |
| super.onStart(); |
| mActivityLifecycleListener.onActivityStarted(this); |
| mClient.enableVideoChannel(true); |
| mClient.getCapabilityManager().addListener(this); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| mActivityLifecycleListener.onActivityResumed(this); |
| mClient.enableVideoChannel(true); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
| // We want to call the change handler with an initial value as onMultiWindowModeChanged |
| // won't be called if the state hasn't changed, such as when the user resizes in |
| // split-screen, and we want to ensure we have a default value set (even though it may |
| // change soon after). |
| onMultiWindowModeChanged(isInMultiWindowMode()); |
| } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| setUpAutoHideToolbar(); |
| syncActionBarToSystemUiState(); |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| if (isFinishing()) { |
| mActivityLifecycleListener.onActivityPaused(this); |
| } |
| super.onPause(); |
| // The activity is paused in windowed mode when the user switches to another window. In |
| // that case we should leave the video channel running so they continue to see updates from |
| // their remote machine. The video channel will be stopped when onStop() is called. |
| if (!mIsInWindowedMode) { |
| mClient.enableVideoChannel(false); |
| } |
| stopActionBarAutoHideTimer(); |
| } |
| |
| @Override |
| protected void onStop() { |
| mClient.getCapabilityManager().removeListener(this); |
| mActivityLifecycleListener.onActivityStopped(this); |
| super.onStop(); |
| mClient.enableVideoChannel(false); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| mRemoteHostDesktop.destroy(); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { |
| super.onMultiWindowModeChanged(isInMultiWindowMode); |
| |
| mIsInWindowedMode = isInMultiWindowMode; |
| if (!mIsInWindowedMode) { |
| setUpAutoHideToolbar(); |
| syncActionBarToSystemUiState(); |
| } else if (mActionBarAutoHideTask != null) { |
| stopActionBarAutoHideTimer(); |
| mActionBarAutoHideTask = null; |
| showSystemUi(); |
| syncActionBarToSystemUiState(); |
| } |
| } |
| |
| /** Called to initialize the action bar. */ |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| getMenuInflater().inflate(R.menu.desktop_actionbar, menu); |
| |
| mActivityLifecycleListener.onActivityCreatedOptionsMenu(this, menu); |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| // We don't need to show a hide ActionBar button if immersive fullscreen is supported. |
| menu.findItem(R.id.actionbar_hide).setVisible(false); |
| |
| // Although the MenuItems are being created here, they do not have any backing Views yet |
| // as those are created just after this method exits. We post an async task to the UI |
| // thread here so that we can attach our interaction listeners shortly after the views |
| // have been created. |
| final Menu menuFinal = menu; |
| new Handler().post(new Runnable() { |
| @Override |
| public void run() { |
| // Attach a listener to the toolbar itself then attach one to each menu item |
| // which has a backing view object. |
| attachToolbarInteractionListenerToView(mToolbar); |
| int items = menuFinal.size(); |
| for (int i = 0; i < items; i++) { |
| int itemId = menuFinal.getItem(i).getItemId(); |
| View menuItemView = findViewById(itemId); |
| if (menuItemView != null) { |
| attachToolbarInteractionListenerToView(menuItemView); |
| } |
| } |
| } |
| }); |
| } |
| |
| ChromotingUtil.tintMenuIcons(this, menu); |
| |
| // Wait to set the input mode until after the default tinting has been applied. |
| setInputMode(mInputMode); |
| |
| // Keyboard state must be set after the keyboard icon has been added to the menu. |
| setKeyboardState(getResources().getConfiguration()); |
| |
| return super.onCreateOptionsMenu(menu); |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration config) { |
| super.onConfigurationChanged(config); |
| setKeyboardState(config); |
| } |
| |
| private void setKeyboardState(Configuration configuration) { |
| mHasPhysicalKeyboard = (configuration.keyboard == Configuration.KEYBOARD_QWERTY) |
| && (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO); |
| mToolbar.getMenu().findItem(R.id.actionbar_keyboard).setVisible(!mHasPhysicalKeyboard); |
| } |
| |
| public Event<SystemUiVisibilityChangedEventParameter> onSystemUiVisibilityChanged() { |
| return mOnSystemUiVisibilityChanged; |
| } |
| |
| public Event<InputModeChangedEventParameter> onInputModeChanged() { |
| return mOnInputModeChanged; |
| } |
| |
| private InputMode getInitialInputModeValue() { |
| // Load the previously-selected input mode from Preferences. |
| // TODO(joedow): Evaluate and determine if we should use a different input mode based on |
| // a device characteristic such as screen size. |
| InputMode inputMode = InputMode.TRACKPAD; |
| String previousInputMode = |
| getPreferences(MODE_PRIVATE) |
| .getString(PREFERENCE_INPUT_MODE, inputMode.name()); |
| |
| try { |
| inputMode = InputMode.valueOf(previousInputMode); |
| } catch (IllegalArgumentException ex) { |
| // Invalid or unexpected value was found, just use the default mode. |
| } |
| |
| return inputMode; |
| } |
| |
| private void setInputMode(InputMode inputMode) { |
| Menu menu = mToolbar.getMenu(); |
| MenuItem trackpadModeMenuItem = menu.findItem(R.id.actionbar_trackpad_mode); |
| MenuItem touchModeMenuItem = menu.findItem(R.id.actionbar_touch_mode); |
| if (inputMode == InputMode.TRACKPAD) { |
| trackpadModeMenuItem.setVisible(true); |
| touchModeMenuItem.setVisible(false); |
| } else if (inputMode == InputMode.TOUCH) { |
| touchModeMenuItem.setVisible(true); |
| trackpadModeMenuItem.setVisible(false); |
| } else { |
| assert false : "Unreached"; |
| return; |
| } |
| |
| mInputMode = inputMode; |
| getPreferences(MODE_PRIVATE) |
| .edit() |
| .putString(PREFERENCE_INPUT_MODE, mInputMode.name()) |
| .apply(); |
| |
| mOnInputModeChanged.raise( |
| new InputModeChangedEventParameter(mInputMode, mHostTouchCapability)); |
| } |
| |
| @Override |
| public void onCapabilitiesChanged(List<String> newCapabilities) { |
| if (newCapabilities.contains(Capabilities.TOUCH_CAPABILITY)) { |
| mHostTouchCapability = CapabilityManager.HostCapability.SUPPORTED; |
| } else { |
| mHostTouchCapability = CapabilityManager.HostCapability.UNSUPPORTED; |
| } |
| |
| mOnInputModeChanged.raise( |
| new InputModeChangedEventParameter(mInputMode, mHostTouchCapability)); |
| } |
| |
| // Any time an onTouchListener is attached, a lint warning about filtering touch events is |
| // generated. Since the function below is only used to listen to, not intercept, the events, |
| // the lint warning can be safely suppressed. |
| @SuppressLint("ClickableViewAccessibility") |
| private void attachToolbarInteractionListenerToView(View view) { |
| view.setOnTouchListener(new OnTouchListener() { |
| @Override |
| public boolean onTouch(View view, MotionEvent event) { |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| stopActionBarAutoHideTimer(); |
| break; |
| |
| case MotionEvent.ACTION_UP: |
| startActionBarAutoHideTimer(); |
| break; |
| |
| default: |
| // Ignore. |
| break; |
| } |
| |
| return false; |
| } |
| }); |
| } |
| |
| private void setUpAutoHideToolbar() { |
| if (mActionBarAutoHideTask != null) { |
| return; |
| } |
| |
| mActionBarAutoHideTask = new Runnable() { |
| public void run() { |
| if (!mToolbar.isOverflowMenuShowing()) { |
| hideSystemUi(); |
| } |
| } |
| }; |
| } |
| |
| // Posts a deplayed task to hide the ActionBar. If an existing task has already been scheduled, |
| // then the previous task is removed and the new one scheduled, effectively resetting the timer. |
| private void startActionBarAutoHideTimer() { |
| if (mActionBarAutoHideTask != null) { |
| stopActionBarAutoHideTimer(); |
| getWindow().getDecorView().postDelayed( |
| mActionBarAutoHideTask, ACTIONBAR_AUTO_HIDE_DELAY_MS); |
| } |
| } |
| |
| // Clear all existing delayed tasks to prevent the ActionBar from being hidden. |
| private void stopActionBarAutoHideTimer() { |
| if (mActionBarAutoHideTask != null) { |
| getWindow().getDecorView().removeCallbacks(mActionBarAutoHideTask); |
| } |
| } |
| |
| // Updates the ActionBar visibility to match the System UI elements. This is useful after a |
| // power or activity lifecycle event in which the current System UI state has changed but we |
| // never received the notification. |
| private void syncActionBarToSystemUiState() { |
| onSystemUiVisibilityChange(getWindow().getDecorView().getSystemUiVisibility()); |
| } |
| |
| private boolean isActionBarVisible() { |
| return getSupportActionBar() != null && getSupportActionBar().isShowing(); |
| } |
| |
| private boolean isSystemUiVisible() { |
| return (getWindow().getDecorView().getSystemUiVisibility() & getFullscreenFlags()) == 0; |
| } |
| |
| /** 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. |
| |
| // Check if the system is in fullscreen/lights-out mode then update the ActionBar to match. |
| int fullscreenFlags = getFullscreenFlags(); |
| if ((visibility & fullscreenFlags) != 0) { |
| hideActionBar(); |
| } else { |
| showActionBar(); |
| } |
| } |
| |
| @SuppressLint("InlinedApi") |
| private static int getFullscreenFlags() { |
| // 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; |
| } |
| return flags; |
| } |
| |
| @SuppressLint("InlinedApi") |
| private static int getLayoutFlags() { |
| int flags = 0; |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
| flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; |
| flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; |
| } |
| return flags; |
| } |
| |
| /** |
| * Shows the soft keyboard if no physical keyboard is attached. |
| */ |
| public void showKeyboard() { |
| if (!mHasPhysicalKeyboard) { |
| UiUtils.showKeyboard(mRemoteHostDesktop); |
| } |
| } |
| |
| public void showSystemUi() { |
| // Request exit from any fullscreen mode. The action-bar controls will be shown in response |
| // to the SystemUiVisibility notification. The visibility of the action-bar should be tied |
| // to the fullscreen state of the system, so there's no need to explicitly show it here. |
| int flags = View.SYSTEM_UI_FLAG_VISIBLE | getLayoutFlags(); |
| getWindow().getDecorView().setSystemUiVisibility(flags); |
| |
| // The OS will not call onSystemUiVisibilityChange() if the soft keyboard is visible which |
| // means our ActionBar will not be shown if this function is called in that scenario. |
| if (mSoftInputVisible) { |
| showActionBar(); |
| } |
| } |
| |
| /** Shows the action bar without changing SystemUiVisibility. */ |
| private void showActionBar() { |
| Animation animation = AnimationUtils.loadAnimation(this, android.R.anim.fade_in); |
| animation.setDuration(ACTIONBAR_ANIMATION_DURATION_MS); |
| mToolbar.startAnimation(animation); |
| |
| getSupportActionBar().show(); |
| startActionBarAutoHideTimer(); |
| } |
| |
| @SuppressLint("InlinedApi") |
| public void hideSystemUi() { |
| // If a soft input device is present, then hide the ActionBar but do not hide the rest of |
| // system UI. A second call will be made once the soft input device is hidden. |
| if (mSoftInputVisible) { |
| hideActionBar(); |
| return; |
| } |
| |
| // Request the device to enter fullscreen mode. Don't hide the controls yet, because the |
| // system might not honor the fullscreen request immediately (for example, if the |
| // keyboard is visible, the system might delay fullscreen until the keyboard is hidden). |
| // The controls will be hidden in response to the SystemUiVisibility notification. |
| // This helps ensure that the visibility of the controls is synchronized with the |
| // fullscreen state. |
| int flags = getFullscreenFlags(); |
| |
| // 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 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; |
| flags |= View.SYSTEM_UI_FLAG_IMMERSIVE; |
| } |
| flags |= getLayoutFlags(); |
| |
| getWindow().getDecorView().setSystemUiVisibility(flags); |
| } |
| |
| /** Hides the action bar without changing SystemUiVisibility. */ |
| private void hideActionBar() { |
| Animation animation = AnimationUtils.loadAnimation(this, android.R.anim.fade_out); |
| animation.setDuration(ACTIONBAR_ANIMATION_DURATION_MS); |
| mToolbar.startAnimation(animation); |
| |
| getSupportActionBar().hide(); |
| stopActionBarAutoHideTimer(); |
| } |
| |
| /** Called whenever an action bar button is pressed. */ |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| int id = item.getItemId(); |
| |
| mActivityLifecycleListener.onActivityOptionsItemSelected(this, item); |
| |
| if (id == R.id.actionbar_trackpad_mode) { |
| // When the trackpad icon is tapped, we want to switch the input mode to touch. |
| setInputMode(InputMode.TOUCH); |
| return true; |
| } |
| if (id == R.id.actionbar_touch_mode) { |
| // When the touch icon is tapped, we want to switch the input mode to trackpad. |
| setInputMode(InputMode.TRACKPAD); |
| return true; |
| } |
| if (id == R.id.actionbar_keyboard) { |
| ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0); |
| return true; |
| } |
| if (id == R.id.actionbar_hide) { |
| hideSystemUi(); |
| return true; |
| } |
| if (id == R.id.actionbar_disconnect || id == android.R.id.home) { |
| mClient.destroy(); |
| return true; |
| } |
| if (id == R.id.actionbar_send_ctrl_alt_del) { |
| mInjector.sendCtrlAltDel(); |
| return true; |
| } |
| if (id == R.id.actionbar_help) { |
| HelpSingleton.getInstance().launchHelp(this, HelpContext.DESKTOP); |
| return true; |
| } |
| return super.onOptionsItemSelected(item); |
| } |
| |
| private void attachSystemUiResizeListener() { |
| View systemUiResizeDetector = findViewById(R.id.resize_detector); |
| systemUiResizeDetector.addOnLayoutChangeListener(new OnLayoutChangeListener() { |
| // Tracks the maximum 'bottom' value seen during layout changes. This value represents |
| // the top of the SystemUI displayed at the bottom of the screen. |
| // Note: This value is a screen coordinate so a larger value means lower on the screen. |
| private int mMaxBottomValue; |
| |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, |
| int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| // As the activity is started, a number of layout changes will flow through. If |
| // this is a fresh start, then we will see one layout change which will represent |
| // the steady state of the UI and will include an accurate 'bottom' value. If we |
| // are transitioning from another activity/orientation, then there may be several |
| // layout change events as the view is updated (i.e. the OSK might have been |
| // displayed previously but is being dismissed). Therefore we want to track the |
| // largest value we have seen and use it to determine if a new system UI (such as |
| // the OSK) is being displayed. |
| if (mMaxBottomValue < bottom) { |
| mMaxBottomValue = bottom; |
| return; |
| } |
| |
| // If the delta between lowest bound we have seen (should be a System UI such as |
| // the navigation bar) and the current bound does not match, then we have a form |
| // of soft input displayed. Note that the size of a soft input device can change |
| // when the input method is changed so we want to send updates to the image canvas |
| // whenever they occur. |
| boolean oldSoftInputVisible = mSoftInputVisible; |
| mSoftInputVisible = (bottom < mMaxBottomValue); |
| |
| // Send the System UI sizes if either the Soft Keyboard is displayed or if we are in |
| // windowed mode and there is System UI present. The user needs to be able to move |
| // the canvas so they can see where they are typing in the first case and in the |
| // second, the System UI is always present so the user needs a way to position the |
| // canvas so all parts of the desktop can be made visible. |
| if (mSoftInputVisible || (mIsInWindowedMode && isSystemUiVisible())) { |
| mOnSystemUiVisibilityChanged.raise( |
| new SystemUiVisibilityChangedEventParameter(left, top, right, bottom)); |
| } else { |
| mOnSystemUiVisibilityChanged.raise( |
| new SystemUiVisibilityChangedEventParameter(0, 0, 0, 0)); |
| } |
| |
| boolean softInputVisibilityChanged = oldSoftInputVisible != mSoftInputVisible; |
| if (!mSoftInputVisible && softInputVisibilityChanged && !isActionBarVisible()) { |
| // Queue a task which will run after the current action (OSK dismiss) has |
| // completed, otherwise the hide request will not take effect. |
| new Handler().post(new Runnable() { |
| @Override |
| public void run() { |
| if (!mSoftInputVisible && !isActionBarVisible()) { |
| hideSystemUi(); |
| } |
| } |
| }); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Called once when a keyboard key is pressed, then again when that same key is released. This |
| * is not guaranteed to be notified of all soft keyboard events: certain keyboards might not |
| * call it at all, while others might skip it in certain situations (e.g. swipe input). |
| */ |
| @Override |
| public boolean dispatchKeyEvent(KeyEvent event) { |
| if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { |
| mClient.destroy(); |
| return super.dispatchKeyEvent(event); |
| } |
| |
| return mInjector.sendKeyEvent(event); |
| } |
| } |