Chromium Code Reviews| Index: remoting/android/java/src/org/chromium/chromoting/Desktop.java |
| diff --git a/remoting/android/java/src/org/chromium/chromoting/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/Desktop.java |
| index ef9c582a3be8d6c67d1b947877b05e72e3c84784..b906a87b80b6bacaec05f5ee65192dbb2b6ae7b2 100644 |
| --- a/remoting/android/java/src/org/chromium/chromoting/Desktop.java |
| +++ b/remoting/android/java/src/org/chromium/chromoting/Desktop.java |
| @@ -11,6 +11,7 @@ import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| +import android.graphics.Rect; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.support.v7.app.AppCompatActivity; |
| @@ -20,6 +21,7 @@ import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.View; |
| +import android.view.View.OnLayoutChangeListener; |
| import android.view.inputmethod.InputMethodManager; |
| import org.chromium.chromoting.cardboard.DesktopActivity; |
| @@ -40,8 +42,10 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| * Preference used for displaying an interestitial dialog only when the user first accesses the |
| * Cardboard function. |
| */ |
| - private static final String PREFERENCE_CARDBOARD_DIALOG_SEEN = |
| - "cardboard_dialog_seen"; |
| + private static final String PREFERENCE_CARDBOARD_DIALOG_SEEN = "cardboard_dialog_seen"; |
| + |
| + /** The amount of time to wait to hide the Actionbar after user input is seen. */ |
| + private static final int ACTIONBAR_AUTO_HIDE_DELAY_MS = 5000; |
| /** The surface that displays the remote host's desktop feed. */ |
| private DesktopView mRemoteHostDesktop; |
| @@ -51,10 +55,15 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| private ActivityLifecycleListener mActivityLifecycleListener; |
| - // Flag to indicate whether the current activity is going to switch to Cardboard |
| - // desktop activity. |
| + /** Flag to indicate whether the current activity is switching to Cardboard desktop activity. */ |
| private boolean mSwitchToCardboardDesktopActivity; |
| + /** 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; |
| + |
| /** Called when the activity is first created. */ |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| @@ -78,9 +87,19 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| View decorView = getWindow().getDecorView(); |
| decorView.setOnSystemUiVisibilityChangeListener(this); |
| - mActivityLifecycleListener = CapabilityManager.getInstance() |
| - .onActivityAcceptingListener(this, Capabilities.CAST_CAPABILITY); |
| + mActivityLifecycleListener = CapabilityManager.getInstance().onActivityAcceptingListener( |
| + this, Capabilities.CAST_CAPABILITY); |
| mActivityLifecycleListener.onActivityCreated(this, savedInstanceState); |
| + |
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| + // 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. |
| + showActionBar(); |
| + attachKeyboardVisibilityListener(); |
| + } else { |
| + mRemoteHostDesktop.setFitsSystemWindows(true); |
| + } |
| } |
| @Override |
| @@ -98,6 +117,7 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| if (!mSwitchToCardboardDesktopActivity) { |
| JniInterface.enableVideoChannel(false); |
| } |
| + stopActionBarAutoHideTimer(); |
| } |
| @Override |
| @@ -105,6 +125,7 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| super.onResume(); |
| mActivityLifecycleListener.onActivityResumed(this); |
| JniInterface.enableVideoChannel(true); |
| + startActionBarAutoHideTimer(); |
| } |
| @Override |
| @@ -127,8 +148,8 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| boolean enableCardboard = false; |
| try { |
| - ApplicationInfo ai = getPackageManager() |
| - .getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); |
| + ApplicationInfo ai = getPackageManager().getApplicationInfo( |
| + getPackageName(), PackageManager.GET_META_DATA); |
| Bundle bundle = ai.metaData; |
| enableCardboard = bundle.getInt("enable_cardboard") == 1; |
| } catch (NameNotFoundException e) { |
| @@ -143,6 +164,41 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| return super.onCreateOptionsMenu(menu); |
| } |
| + // 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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
| + // The required flags for immersive full screen are not supported on pre-KitKat builds |
| + // so don't enable the auto-hide timer for them as every transition on older versions |
| + // causes resizing of the canvas and we want to minimize the number of layout changes. |
| + return; |
| + } |
| + |
| + if (mActionBarAutoHideTask != null) { |
| + stopActionBarAutoHideTimer(); |
| + } |
| + |
| + mActionBarAutoHideTask = new Runnable() { |
|
Lambros
2015/10/29 23:39:16
optional: Don't really need to keep setting mActio
joedow
2015/10/30 17:41:22
Done.
|
| + public void run() { |
| + hideActionBar(); |
| + } |
| + }; |
| + |
| + View decorView = getWindow().getDecorView(); |
| + decorView.postDelayed(mActionBarAutoHideTask, ACTIONBAR_AUTO_HIDE_DELAY_MS); |
| + } |
| + |
| + // Clear an existing delayed task to prevent the ActionBar from being hidden. |
| + private void stopActionBarAutoHideTimer() { |
| + if (mActionBarAutoHideTask == null) { |
| + return; |
| + } |
| + |
| + View decorView = getWindow().getDecorView(); |
| + decorView.removeCallbacks(mActionBarAutoHideTask); |
| + mActionBarAutoHideTask = null; |
| + } |
| + |
| /** Called whenever the visibility of the system status bar or navigation bar changes. */ |
| @Override |
| public void onSystemUiVisibilityChange(int visibility) { |
| @@ -176,12 +232,26 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| // 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. |
| View decorView = getWindow().getDecorView(); |
| - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); |
| + int flags = View.SYSTEM_UI_FLAG_VISIBLE; |
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
| + flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
|
Lambros
2015/10/29 23:39:16
Since these are duplicated below, maybe have a get
joedow
2015/10/30 17:41:23
Done.
|
| + flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; |
| + flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; |
| + } |
| + decorView.setSystemUiVisibility(flags); |
| + |
| + // The OS will not call onSystemUiVisibilityChange() if the keyboard is visible which means |
| + // our ActionBar will not be visible until then. This check allows us to work around this |
| + // issue and still allow the system to show the ActionBar normally with the soft keyboard. |
| + if (mSoftInputVisible) { |
| + showActionBarWithoutSystemUi(); |
| + } |
| } |
| /** Shows the action bar without changing SystemUiVisibility. */ |
| private void showActionBarWithoutSystemUi() { |
| getSupportActionBar().show(); |
| + startActionBarAutoHideTimer(); |
| } |
| @SuppressLint("InlinedApi") |
| @@ -204,15 +274,28 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| // 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); |
| + flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; |
| + flags |= View.SYSTEM_UI_FLAG_IMMERSIVE; |
| + flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
| + flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; |
| + flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE; |
| } |
| decorView.setSystemUiVisibility(flags); |
| + |
| + // The OS will not call onSystemUiVisibilityChange() until the keyboard has been dismissed |
| + // which means our ActionBar will still be visible. This check allows us to work around |
| + // this issue when the keyboard is visible and the user wants additional space on the screen |
| + // and still allow the system to hide the ActionBar normally when no keyboard is present. |
| + if (mSoftInputVisible) { |
| + hideActionBarWithoutSystemUi(); |
| + } |
| } |
| /** Hides the action bar without changing SystemUiVisibility. */ |
| private void hideActionBarWithoutSystemUi() { |
| getSupportActionBar().hide(); |
| + stopActionBarAutoHideTimer(); |
| } |
| /** Called whenever an action bar button is pressed. */ |
| @@ -222,6 +305,9 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| mActivityLifecycleListener.onActivityOptionsItemSelected(this, item); |
| + // Whenever a user selects an option from the ActionBar, reset the auto-hide timer. |
| + startActionBarAutoHideTimer(); |
| + |
| if (id == R.id.actionbar_cardboard) { |
| onCardboardItemSelected(); |
| return true; |
| @@ -240,9 +326,8 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| } |
| if (id == R.id.actionbar_send_ctrl_alt_del) { |
| int[] keys = { |
| - KeyEvent.KEYCODE_CTRL_LEFT, |
| - KeyEvent.KEYCODE_ALT_LEFT, |
| - KeyEvent.KEYCODE_FORWARD_DEL, |
| + KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_ALT_LEFT, |
| + KeyEvent.KEYCODE_FORWARD_DEL, |
| }; |
| for (int key : keys) { |
| JniInterface.sendKeyEvent(0, key, true); |
| @@ -259,6 +344,35 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| return super.onOptionsItemSelected(item); |
| } |
| + private void attachKeyboardVisibilityListener() { |
| + View keyboardVisibilityDetector = findViewById(R.id.resize_detector); |
| + keyboardVisibilityDetector.addOnLayoutChangeListener(new OnLayoutChangeListener() { |
| + // Represents the initial size of the viewable area sans the soft keyboard. |
| + private Rect mInitialRect; |
| + |
| + @Override |
| + public void onLayoutChange(View v, int left, int top, int right, int bottom, |
| + int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| + if (mInitialRect == null || bottom > mInitialRect.bottom) { |
|
Lambros
2015/10/29 23:39:16
Maybe initialize mInitialRect and mutate it instea
joedow
2015/10/30 17:41:22
Done.
|
| + // When the activity is started fresh we will see a single layout change event, |
| + // however if we are transitioning between orientations and the keyboard was |
| + // previously visible, we might receive several layout changes sequentially. |
| + mInitialRect = new Rect(left, top, right, bottom); |
| + return; |
| + } |
| + |
| + // If the delta between lowest bound we have seen (should be a systemUI 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. |
| + mSoftInputVisible = ((mInitialRect.bottom - bottom) > 0); |
|
Lambros
2015/10/29 23:39:16
Maybe replace Rect with int, since you only use |b
joedow
2015/10/30 17:41:22
Done.
|
| + mRemoteHostDesktop.onSoftInputMethodVisibilityChanged( |
| + mSoftInputVisible, new Rect(left, top, right, bottom)); |
| + } |
| + }); |
| + } |
| + |
| private void onCardboardItemSelected() { |
| if (getPreferences(MODE_PRIVATE).getBoolean(PREFERENCE_CARDBOARD_DIALOG_SEEN, false)) { |
| switchToCardboardMode(); |
| @@ -269,21 +383,22 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| .setTitle(getTitle()) |
| .setMessage(R.string.cardboard_warning_message) |
| .setIcon(R.drawable.ic_cardboard) |
| - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { |
| - @Override |
| - public void onClick(DialogInterface dialog, int id) { |
| - getPreferences(MODE_PRIVATE) |
| - .edit() |
| - .putBoolean(PREFERENCE_CARDBOARD_DIALOG_SEEN, true) |
| - .apply(); |
| - switchToCardboardMode(); |
| - } |
| - }) |
| - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { |
| - @Override |
| - public void onClick(DialogInterface dialog, int id) { |
| - } |
| - }) |
| + .setPositiveButton(android.R.string.ok, |
| + new DialogInterface.OnClickListener() { |
| + @Override |
| + public void onClick(DialogInterface dialog, int id) { |
| + getPreferences(MODE_PRIVATE) |
| + .edit() |
| + .putBoolean(PREFERENCE_CARDBOARD_DIALOG_SEEN, true) |
| + .apply(); |
| + switchToCardboardMode(); |
| + } |
| + }) |
| + .setNegativeButton(android.R.string.cancel, |
| + new DialogInterface.OnClickListener() { |
| + @Override |
| + public void onClick(DialogInterface dialog, int id) {} |
| + }) |
| .create() |
| .show(); |
| } |
| @@ -336,12 +451,12 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil |
| // want to send it as KeyEvent. |
| int unicode = keyCode != KeyEvent.KEYCODE_ENTER ? event.getUnicodeChar() : 0; |
| - boolean no_modifiers = !event.isAltPressed() |
| - && !event.isCtrlPressed() && !event.isMetaPressed(); |
| + boolean no_modifiers = |
| + !event.isAltPressed() && !event.isCtrlPressed() && !event.isMetaPressed(); |
| if (pressed && unicode != 0 && no_modifiers) { |
| mPressedTextKeys.add(keyCode); |
| - int[] codePoints = { unicode }; |
| + int[] codePoints = {unicode}; |
| JniInterface.sendTextEvent(new String(codePoints, 0, 1)); |
| return true; |
| } |