diff options
author | joedow <joedow@chromium.org> | 2015-12-02 13:40:33 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-12-02 21:41:13 +0000 |
commit | cf895fda588d81c9d1b4fa8ab572750da2fbaae7 (patch) | |
tree | 8ea3b88179536b6789e8f6f59056949c47bc690c /remoting/android | |
parent | b023bc744eaa8b821ba69269c832c646ddc54dd4 (diff) | |
download | chromium_src-cf895fda588d81c9d1b4fa8ab572750da2fbaae7.zip chromium_src-cf895fda588d81c9d1b4fa8ab572750da2fbaae7.tar.gz chromium_src-cf895fda588d81c9d1b4fa8ab572750da2fbaae7.tar.bz2 |
Adding UX for toggling Touch Input Mode.
This change is needed to allow toggling the input mode via the UX. I
have tested it but am leaving it disabled until my next changelist which
will add the new input modes.
Included in this change is:
- CapabilityManager updated to allow other classes to listen for
change events.
- Fixed a problem with the CapabilityManager where it would continue
to return capabilities for the previous host until a new connection
was made.
- Added state information to the Desktop Activity to track whether the
remote host supports touch injection and the currently selected input mode.
- InputMode changes are persisted between sessions using SharedPreferences
- Added a function which will handle creating a new InputStrategy when
the user toggles input modes.
There are two interesting pieces of logic in this change.
- Desktop needs to know both the current InputMode and the host
capabilities and it will wait to tell the DesktopView which
InputStrategy to use until it knows both
- The Host Capabilities are not known until after creation time so I
updated CapabilityManager to use callbacks to signal changes to the
negotiated capabilities.
BUG=454549
Review URL: https://codereview.chromium.org/1490453002
Cr-Commit-Position: refs/heads/master@{#362798}
Diffstat (limited to 'remoting/android')
6 files changed, 217 insertions, 12 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/Capabilities.java b/remoting/android/java/src/org/chromium/chromoting/Capabilities.java index cff3d9e..294a242 100644 --- a/remoting/android/java/src/org/chromium/chromoting/Capabilities.java +++ b/remoting/android/java/src/org/chromium/chromoting/Capabilities.java @@ -12,4 +12,5 @@ package org.chromium.chromoting; */ public class Capabilities { public static final String CAST_CAPABILITY = "casting"; + public static final String TOUCH_CAPABILITY = "touchEvents"; } diff --git a/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java b/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java index 939817d..e332e83 100644 --- a/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java +++ b/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java @@ -27,6 +27,27 @@ import java.util.List; * different capabilities at different stages of the application. */ public class CapabilityManager { + /** Used to allow objects to receive notifications when the host capabilites are received. */ + public interface CapabilitiesChangedListener { + void onCapabilitiesChanged(List<String> newCapabilities); + } + + /** Tracks whether the remote host supports a capability. */ + public enum HostCapability { + UNKNOWN, + SUPPORTED, + UNSUPPORTED; + + public boolean isSet() { + return this != UNKNOWN; + } + + public boolean isSupported() { + assert isSet(); + return this == SUPPORTED; + } + } + private static final String TAG = "Chromoting"; /** Lazily-initialized singleton object that can be used from different Activities. */ @@ -44,11 +65,17 @@ public class CapabilityManager { /** List of extensions to the client based on capabilities negotiated with the host. */ private List<ClientExtension> mClientExtensions; + /** Maintains a list of listeners to notify when host capabilities are received. */ + private List<CapabilitiesChangedListener> mCapabilitiesChangedListeners; + private CapabilityManager() { mLocalCapabilities = new ArrayList<String>(); mClientExtensions = new ArrayList<ClientExtension>(); mLocalCapabilities.add(Capabilities.CAST_CAPABILITY); + mLocalCapabilities.add(Capabilities.TOUCH_CAPABILITY); + + mCapabilitiesChangedListeners = new ArrayList<CapabilitiesChangedListener>(); } /** @@ -64,6 +91,13 @@ public class CapabilityManager { } /** + * Cleans up host specific state when the connection has been terminated. + */ + public void onHostDisconnect() { + mNegotiatedCapabilities = null; + } + + /** * Returns a space-separated list (required by host) of the capabilities supported by * this client. */ @@ -72,6 +106,30 @@ public class CapabilityManager { } /** + * Registers the given listener object so it is notified when host capabilities are negotiated. + */ + public void addListener(CapabilitiesChangedListener listener) { + assert !mCapabilitiesChangedListeners.contains(listener); + mCapabilitiesChangedListeners.add(listener); + + // If we have already received the host capabilities before this listener was registered, + // then fire the event for this listener immediately. + if (mNegotiatedCapabilities != null) { + // Clone the capabilities list passed to the caller to prevent them from mutating it. + listener.onCapabilitiesChanged(new ArrayList<>(mNegotiatedCapabilities)); + } + } + + /** + * Removes the given listener object from the list of change listeners. + */ + public void removeListener(CapabilitiesChangedListener listener) { + assert mCapabilitiesChangedListeners.contains(listener); + + mCapabilitiesChangedListeners.remove(listener); + } + + /** * Returns the ActivityLifecycleListener associated with the specified capability, if * |capability| is enabled and such a listener exists. * @@ -97,10 +155,8 @@ public class CapabilityManager { } /** - * Receives the capabilities negotiated between client and host and creates the appropriate - * extension handlers. - * - * Currently only the CAST_CAPABILITY exists, so that is the only extension constructed. + * Receives the capabilities negotiated between client and host, creates the appropriate + * extension handlers, and notifies registered listeners of the change. */ public void setNegotiatedCapabilities(String capabilities) { mNegotiatedCapabilities = Arrays.asList(capabilities.split(" ")); @@ -108,6 +164,15 @@ public class CapabilityManager { if (isCapabilityEnabled(Capabilities.CAST_CAPABILITY)) { mClientExtensions.add(maybeCreateCastExtensionHandler()); } + + // Clone the list of listeners to prevent problems if the callback calls back into this + // object and removes itself from the list of listeners. + List<CapabilitiesChangedListener> listeners = + new ArrayList<>(mCapabilitiesChangedListeners); + for (CapabilitiesChangedListener listener : listeners) { + // Clone the capabilities list passed to the caller to prevent them from mutating it. + listener.onCapabilitiesChanged(new ArrayList<>(mNegotiatedCapabilities)); + } } /** diff --git a/remoting/android/java/src/org/chromium/chromoting/ChromotingUtil.java b/remoting/android/java/src/org/chromium/chromoting/ChromotingUtil.java index 64e0008..0f26ea7 100644 --- a/remoting/android/java/src/org/chromium/chromoting/ChromotingUtil.java +++ b/remoting/android/java/src/org/chromium/chromoting/ChromotingUtil.java @@ -10,6 +10,7 @@ import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.Menu; +import android.view.MenuItem; import org.chromium.base.ApiCompatibilityUtils; @@ -27,10 +28,19 @@ public abstract class ChromotingUtil { int color = getColorAttribute(context, R.attr.colorControlNormal); int items = menu.size(); for (int i = 0; i < items; i++) { - Drawable icon = menu.getItem(i).getIcon(); - if (icon != null) { - icon.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); - } + ChromotingUtil.tintMenuIcon(menu.getItem(i), color); + } + } + + /** + * Sets a color filter on the specified MenuItem. + * @param menuItem MenuItem to tint. + * @param color Color to set on the menuItem. + */ + public static void tintMenuIcon(MenuItem menuItem, int color) { + Drawable icon = menuItem.getIcon(); + if (icon != null) { + icon.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN); } } diff --git a/remoting/android/java/src/org/chromium/chromoting/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/Desktop.java index e7b2628..99ef605 100644 --- a/remoting/android/java/src/org/chromium/chromoting/Desktop.java +++ b/remoting/android/java/src/org/chromium/chromoting/Desktop.java @@ -33,19 +33,36 @@ import org.chromium.chromoting.help.HelpContext; import org.chromium.chromoting.help.HelpSingleton; import org.chromium.chromoting.jni.JniInterface; +import java.util.List; import java.util.Set; import java.util.TreeSet; /** * A simple screen that does nothing except display a DesktopView and notify it of rotations. */ -public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibilityChangeListener { +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 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"; + /** 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; @@ -69,6 +86,13 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil /** 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; + /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { @@ -101,6 +125,8 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil this, Capabilities.CAST_CAPABILITY); mActivityLifecycleListener.onActivityCreated(this, savedInstanceState); + mInputMode = getInitialInputModeValue(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { attachKeyboardVisibilityListener(); @@ -136,6 +162,7 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil mActivityLifecycleListener.onActivityStarted(this); JniInterface.enableVideoChannel(true); mRemoteHostDesktop.attachRedrawCallback(); + CapabilityManager.getInstance().addListener(this); } @Override @@ -158,6 +185,7 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil @Override protected void onStop() { + CapabilityManager.getInstance().removeListener(this); mActivityLifecycleListener.onActivityStopped(this); super.onStop(); if (mSwitchToCardboardDesktopActivity) { @@ -219,9 +247,80 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil ChromotingUtil.tintMenuIcons(this, menu); + // Wait to set the input mode until after the default tinting has been applied. + setInputMode(mInputMode); + return super.onCreateOptionsMenu(menu); } + 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 toggleInputMode() { + if (mInputMode == InputMode.TRACKPAD) { + setInputMode(InputMode.TOUCH); + } else if (mInputMode == InputMode.TOUCH) { + setInputMode(InputMode.TRACKPAD); + } + } + + private void setInputMode(InputMode inputMode) { + String titleText; + int inputModeItemColorId; + if (inputMode == InputMode.TRACKPAD) { + titleText = getString(R.string.select_trackpad_mode); + inputModeItemColorId = R.attr.colorControlNormal; + } else if (inputMode == InputMode.TOUCH) { + titleText = getString(R.string.select_touch_mode); + inputModeItemColorId = R.attr.colorControlActivated; + } else { + assert false : "Unreached"; + return; + } + + mInputMode = inputMode; + getPreferences(MODE_PRIVATE) + .edit() + .putString(PREFERENCE_INPUT_MODE, mInputMode.name()) + .apply(); + + Menu menu = mToolbar.getMenu(); + MenuItem inputModeMenuItem = menu.findItem(R.id.actionbar_input_mode); + if (inputModeMenuItem != null) { + inputModeMenuItem.setTitle(titleText); + int color = ChromotingUtil.getColorAttribute(this, inputModeItemColorId); + ChromotingUtil.tintMenuIcon(inputModeMenuItem, color); + } + + mRemoteHostDesktop.changeInputMode(mInputMode, mHostTouchCapability); + } + + @Override + public void onCapabilitiesChanged(List<String> newCapabilities) { + if (newCapabilities.contains(Capabilities.TOUCH_CAPABILITY)) { + mHostTouchCapability = CapabilityManager.HostCapability.SUPPORTED; + } else { + mHostTouchCapability = CapabilityManager.HostCapability.UNSUPPORTED; + } + + mRemoteHostDesktop.changeInputMode(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. @@ -378,6 +477,10 @@ public class Desktop extends AppCompatActivity implements View.OnSystemUiVisibil onCardboardItemSelected(); return true; } + if (id == R.id.actionbar_input_mode) { + toggleInputMode(); + return true; + } if (id == R.id.actionbar_keyboard) { ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0); return true; diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java index 466012b..d2499be 100644 --- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java +++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java @@ -159,9 +159,6 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, mRenderData = new RenderData(); mInputHandler = new TouchInputHandler(this, context, mRenderData); - // TODO(joedow): Move this into a method which will choose the correct input strategy - // based on host support and current toggle setting in the UI. - mInputHandler.setInputStrategy(new TrackpadInputStrategy(this, mRenderData)); mRepaintPending = false; @@ -392,4 +389,32 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, mInputAnimationRunning = enabled; } } + + /** Updates the current InputStrategy used by the TouchInputHandler. */ + public void changeInputMode( + Desktop.InputMode inputMode, CapabilityManager.HostCapability hostTouchCapability) { + // In order to set the correct input strategy, we need to know the current input mode and + // the host input capabilities. + if (!inputMode.isSet() || !hostTouchCapability.isSet()) { + return; + } + + switch (inputMode) { + case TRACKPAD: + mInputHandler.setInputStrategy(new TrackpadInputStrategy(this, mRenderData)); + break; + + case TOUCH: + if (hostTouchCapability.isSupported()) { + // TODO(joedow): Set the touch input strategy. + } else { + // TODO(joedow): Set the simulated touch input strategy. + } + break; + + default: + // Unreachable, but required by Google Java style and findbugs. + assert false : "Unreached"; + } + } } diff --git a/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java b/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java index 69dbdba..288fd78 100644 --- a/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java +++ b/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java @@ -210,6 +210,7 @@ public class JniInterface { nativeDisconnect(); sConnectionListener = null; sConnected = false; + sCapabilityManager.onHostDisconnect(); // Drop the reference to free the Bitmap for GC. synchronized (sFrameLock) { |